├── .github ├── configs │ ├── .htpasswd │ ├── default.conf │ ├── nginx.cnf │ ├── pg_hba.conf │ └── zabbix.conf.php ├── scripts │ ├── additional_api_tests.py │ ├── check_new_zabbx_release.py │ ├── compatibility_api_test_5.py │ ├── compatibility_api_test_6.py │ ├── compatibility_api_test_7.py │ ├── compatibility_api_test_latest.py │ ├── depricated_tests.py │ ├── integration_aioapi_test.py │ ├── integration_aiogetter_test.py │ ├── integration_aiosender_test.py │ ├── integration_api_test.py │ ├── integration_getter_test.py │ ├── integration_sender_test.py │ ├── library_import_tests.sh │ ├── release_notification.py │ ├── telegram_msg.py │ └── wait_instance_zabbix.py └── workflows │ ├── additional_tests.yaml │ ├── check_new_release.yaml │ ├── compatibility_50.yaml │ ├── compatibility_60.yaml │ ├── compatibility_70.yaml │ ├── compatibility_72.yaml │ ├── compatibility_latest.yaml │ ├── coverage.yaml │ ├── depricated_tests.yaml │ ├── integration_api.yaml │ ├── integration_getter.yaml │ ├── integration_sender.yaml │ ├── release.yaml │ └── tests.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── api │ ├── asynchronous │ │ ├── auth_by_token.py │ │ ├── check_auth_state.py │ │ ├── clear_history.py │ │ ├── custom_client_session.py │ │ ├── custom_ssl_context.py │ │ ├── delete_items.py │ │ ├── disabling_validate_certs.py │ │ ├── export_templates.py │ │ ├── get_request_parameters.py │ │ ├── use_context_manager.py │ │ └── using_http_auth.py │ └── synchronous │ │ ├── auth_by_token.py │ │ ├── check_auth_state.py │ │ ├── clear_history.py │ │ ├── custom_ssl_context.py │ │ ├── delete_items.py │ │ ├── disabling_validate_certs.py │ │ ├── export_templates.py │ │ ├── get_request_parameters.py │ │ ├── token_auth_if_supported.py │ │ ├── use_context_manager.py │ │ └── using_http_auth.py ├── get │ ├── asynchronous │ │ ├── custom_source_ip.py │ │ └── getting_value.py │ └── synchronous │ │ ├── custom_source_ip.py │ │ ├── getting_value.py │ │ └── psk_wrapper.py └── sender │ ├── asynchronous │ ├── agent_clusters_using.py │ ├── agent_config_using.py │ ├── bulk_sending.py │ ├── custom_source_ip.py │ ├── single_sending.py │ ├── tls_cert_context.py │ └── tls_cert_context_from_config.py │ └── synchronous │ ├── agent_clusters_using.py │ ├── agent_config_using.py │ ├── bulk_sending.py │ ├── custom_source_ip.py │ ├── psk_wrapper.py │ ├── psk_wrapper_from_config.py │ ├── single_sending.py │ ├── tls_cert_wrapper.py │ └── tls_cert_wrapper_from_config.py ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── common.py ├── test_zabbix_aioapi.py ├── test_zabbix_aiogetter.py ├── test_zabbix_aiosender.py ├── test_zabbix_api.py ├── test_zabbix_common.py ├── test_zabbix_getter.py ├── test_zabbix_sender.py └── test_zabbix_types.py └── zabbix_utils ├── __init__.py ├── aioapi.py ├── aiogetter.py ├── aiosender.py ├── api.py ├── common.py ├── exceptions.py ├── getter.py ├── logger.py ├── sender.py ├── types.py └── version.py /.github/configs/.htpasswd: -------------------------------------------------------------------------------- 1 | http_user:$apr1$aeHewW8J$rzWpr44lG7WSrsJRFflWS1 2 | -------------------------------------------------------------------------------- /.github/configs/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default; 3 | listen 443 ssl; 4 | root /var/www/html/; 5 | index index.php; 6 | 7 | server_name localhost 127.0.0.1; 8 | ssl_certificate /etc/nginx/ssl/nginx.crt; 9 | ssl_certificate_key /etc/nginx/ssl/nginx.key; 10 | 11 | location /http_auth/ { 12 | auth_basic "Zabbix HTTP Auth"; 13 | auth_basic_user_file /etc/nginx/.htpasswd; 14 | 15 | proxy_pass http://localhost:8080/; 16 | proxy_set_header Host $http_host; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_buffering on; 20 | } 21 | 22 | location /ssl_context/ { 23 | proxy_pass http://localhost:8080/; 24 | proxy_set_header Host $http_host; 25 | proxy_set_header X-Real-IP $remote_addr; 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | proxy_buffering on; 28 | } 29 | } -------------------------------------------------------------------------------- /.github/configs/nginx.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | default_bits = 2048 3 | distinguished_name = req_distinguished_name 4 | req_extensions = req_ext 5 | x509_extensions = v3_req 6 | prompt = no 7 | [req_distinguished_name] 8 | countryName = LV 9 | localityName = Riga 10 | organizationName = Zabbix SIA 11 | organizationalUnitName = Integration team 12 | emailAddress = integrationteam@zabbix.com 13 | [req_ext] 14 | subjectAltName = @alt_names 15 | [v3_req] 16 | subjectAltName = @alt_names 17 | [alt_names] 18 | DNS.1 = localhost 19 | IP.1 = 127.0.0.1 -------------------------------------------------------------------------------- /.github/configs/pg_hba.conf: -------------------------------------------------------------------------------- 1 | local all postgres peer 2 | host zabbix zabbix 127.0.0.1/32 trust 3 | host zabbix zabbix localhost trust 4 | host zabbix zabbix 0.0.0.0/0 trust 5 | local all all peer 6 | host all all 127.0.0.1/32 scram-sha-256 7 | host all all ::1/128 scram-sha-256 8 | local replication all peer 9 | host replication all 127.0.0.1/32 scram-sha-256 10 | host replication all ::1/128 scram-sha-256 -------------------------------------------------------------------------------- /.github/configs/zabbix.conf.php: -------------------------------------------------------------------------------- 1 | zabbix_utils library supports <={sver} but the latest version of Zabbix is {max(versions)}. 44 | What to do next? Look at the manual: {MANUAL_REPO}. 45 | """ 46 | sys.exit(error) 47 | -------------------------------------------------------------------------------- /.github/scripts/depricated_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import base64 9 | import unittest 10 | 11 | sys.path.append('.') 12 | from zabbix_utils.api import ZabbixAPI 13 | from zabbix_utils.types import APIVersion 14 | from zabbix_utils.aioapi import AsyncZabbixAPI 15 | 16 | ZABBIX_URL = 'https://127.0.0.1:443' 17 | ZABBIX_USER = 'Admin' 18 | ZABBIX_PASSWORD = 'zabbix' 19 | HTTP_USER = 'http_user' 20 | HTTP_PASSWORD = 'http_pass' 21 | 22 | 23 | class BasicAuthAPITest(unittest.TestCase): 24 | """Test working with a real Zabbix API instance using Basic auth synchronously 25 | 26 | Should be removed after: `June 30, 2029` 27 | """ 28 | 29 | def setUp(self): 30 | self.user = ZABBIX_USER 31 | self.password = ZABBIX_PASSWORD 32 | self.url = ZABBIX_URL + '/http_auth/' 33 | self.api = ZabbixAPI( 34 | url=self.url, 35 | user=self.user, 36 | password=self.password, 37 | validate_certs=False, 38 | http_user=HTTP_USER, 39 | http_password=HTTP_PASSWORD 40 | ) 41 | 42 | def tearDown(self): 43 | if self.api: 44 | self.api.logout() 45 | 46 | def test_login(self): 47 | """Tests login function works properly""" 48 | 49 | self.assertEqual( 50 | type(self.api), ZabbixAPI, "Login was going wrong") 51 | self.assertEqual( 52 | type(self.api.api_version()), APIVersion, "Version getting was going wrong") 53 | 54 | def test_basic_auth(self): 55 | """Tests __basic_auth function works properly""" 56 | 57 | self.assertEqual( 58 | self.api._ZabbixAPI__basic_cred, base64.b64encode( 59 | "http_user:http_pass".encode() 60 | ).decode(), "Basic auth credentials generation was going wrong") 61 | 62 | def test_version_get(self): 63 | """Tests getting version info works properly""" 64 | 65 | version = None 66 | if self.api: 67 | version = self.api.apiinfo.version() 68 | self.assertEqual( 69 | version, str(self.api.api_version()), "Request apiinfo.version was going wrong") 70 | 71 | def test_check_auth(self): 72 | """Tests checking authentication state works properly""" 73 | 74 | resp = None 75 | if self.api: 76 | if self.api._ZabbixAPI__session_id == self.api._ZabbixAPI__token: 77 | resp = self.api.user.checkAuthentication(token=self.api._ZabbixAPI__session_id) 78 | else: 79 | resp = self.api.user.checkAuthentication(sessionid=self.api._ZabbixAPI__session_id) 80 | self.assertEqual( 81 | type(resp), dict, "Request user.checkAuthentication was going wrong") 82 | 83 | def test_user_get(self): 84 | """Tests getting users info works properly""" 85 | 86 | users = None 87 | if self.api: 88 | users = self.api.user.get( 89 | output=['userid', 'name'] 90 | ) 91 | self.assertEqual(type(users), list, "Request user.get was going wrong") 92 | 93 | 94 | class BasicAuthAsyncAPITest(unittest.IsolatedAsyncioTestCase): 95 | """Test working with a real Zabbix API instance using Basic auth asynchronously 96 | 97 | Should be removed after: `June 30, 2029` 98 | """ 99 | 100 | async def asyncSetUp(self): 101 | self.user = ZABBIX_USER 102 | self.password = ZABBIX_PASSWORD 103 | self.url = ZABBIX_URL + '/http_auth/' 104 | self.api = AsyncZabbixAPI( 105 | url=self.url, 106 | validate_certs=False, 107 | http_user=HTTP_USER, 108 | http_password=HTTP_PASSWORD 109 | ) 110 | await self.api.login( 111 | user=self.user, 112 | password=self.password 113 | ) 114 | 115 | async def asyncTearDown(self): 116 | if self.api: 117 | await self.api.logout() 118 | 119 | async def test_login(self): 120 | """Tests login function works properly""" 121 | 122 | self.assertEqual( 123 | type(self.api), AsyncZabbixAPI, "Login was going wrong") 124 | self.assertEqual( 125 | type(self.api.api_version()), APIVersion, "Version getting was going wrong") 126 | 127 | async def test_basic_auth(self): 128 | """Tests __basic_auth function works properly""" 129 | 130 | basic_auth = self.api.client_session._default_auth 131 | 132 | self.assertEqual( 133 | base64.b64encode(f"{basic_auth.login}:{basic_auth.password}".encode()).decode(), 134 | base64.b64encode(f"{HTTP_USER}:{HTTP_PASSWORD}".encode()).decode(), 135 | "Basic auth credentials generation was going wrong" 136 | ) 137 | 138 | async def test_version_get(self): 139 | """Tests getting version info works properly""" 140 | 141 | version = None 142 | if self.api: 143 | version = await self.api.apiinfo.version() 144 | self.assertEqual( 145 | version, str(self.api.api_version()), "Request apiinfo.version was going wrong") 146 | 147 | async def test_check_auth(self): 148 | """Tests checking authentication state works properly""" 149 | 150 | resp = None 151 | if self.api: 152 | if self.api._AsyncZabbixAPI__session_id == self.api._AsyncZabbixAPI__token: 153 | resp = await self.api.user.checkAuthentication(token=(self.api._AsyncZabbixAPI__session_id or '')) 154 | else: 155 | resp = await self.api.user.checkAuthentication(sessionid=(self.api._AsyncZabbixAPI__session_id or '')) 156 | self.assertEqual( 157 | type(resp), dict, "Request user.checkAuthentication was going wrong") 158 | 159 | async def test_user_get(self): 160 | """Tests getting users info works properly""" 161 | 162 | users = None 163 | if self.api: 164 | users = await self.api.user.get( 165 | output=['userid', 'name'] 166 | ) 167 | self.assertEqual(type(users), list, "Request user.get was going wrong") 168 | 169 | 170 | if __name__ == '__main__': 171 | unittest.main() 172 | -------------------------------------------------------------------------------- /.github/scripts/integration_aioapi_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import unittest 9 | 10 | sys.path.append('.') 11 | from zabbix_utils.aioapi import AsyncZabbixAPI 12 | from zabbix_utils.types import APIVersion 13 | 14 | 15 | class IntegrationAPITest(unittest.IsolatedAsyncioTestCase): 16 | """Test working with a real Zabbix API instance""" 17 | 18 | async def asyncSetUp(self): 19 | self.url = 'localhost' 20 | self.user = 'Admin' 21 | self.password = 'zabbix' 22 | self.zapi = AsyncZabbixAPI( 23 | url=self.url, 24 | skip_version_check=True 25 | ) 26 | await self.zapi.login( 27 | user=self.user, 28 | password=self.password 29 | ) 30 | 31 | async def asyncTearDown(self): 32 | if self.zapi: 33 | await self.zapi.logout() 34 | 35 | async def test_login(self): 36 | """Tests login function works properly""" 37 | 38 | self.assertEqual( 39 | type(self.zapi), AsyncZabbixAPI, "Login was going wrong") 40 | self.assertEqual( 41 | type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") 42 | 43 | await self.zapi.logout() 44 | 45 | async def test_version_get(self): 46 | """Tests getting version info works properly""" 47 | 48 | version = None 49 | if self.zapi: 50 | version = await self.zapi.apiinfo.version() 51 | self.assertEqual( 52 | version, str(self.zapi.api_version()), "Request apiinfo.version was going wrong") 53 | 54 | async def test_check_auth(self): 55 | """Tests checking authentication state works properly""" 56 | 57 | resp = None 58 | if self.zapi: 59 | if self.zapi._AsyncZabbixAPI__session_id == self.zapi._AsyncZabbixAPI__token: 60 | resp = await self.zapi.user.checkAuthentication(token=self.zapi._AsyncZabbixAPI__session_id) 61 | else: 62 | resp = await self.zapi.user.checkAuthentication(sessionid=self.zapi._AsyncZabbixAPI__session_id) 63 | self.assertEqual( 64 | type(resp), dict, "Request user.checkAuthentication was going wrong") 65 | 66 | async def test_user_get(self): 67 | """Tests getting users info works properly""" 68 | 69 | users = None 70 | if self.zapi: 71 | users = await self.zapi.user.get( 72 | output=['userid', 'name'] 73 | ) 74 | self.assertEqual(type(users), list, "Request user.get was going wrong") 75 | 76 | async def test_host_get(self): 77 | """Tests getting hosts info works properly using suffix""" 78 | 79 | hosts = None 80 | if self.zapi: 81 | hosts = await self.zapi.host_.get_( 82 | output=['hostid', 'host'] 83 | ) 84 | self.assertEqual(type(hosts), list, "Request host.get was going wrong") 85 | 86 | 87 | if __name__ == '__main__': 88 | unittest.main() 89 | -------------------------------------------------------------------------------- /.github/scripts/integration_aiogetter_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import json 9 | import unittest 10 | 11 | sys.path.append('.') 12 | from zabbix_utils.aiogetter import AsyncGetter 13 | 14 | 15 | class IntegrationGetTest(unittest.IsolatedAsyncioTestCase): 16 | """Test working with a real Zabbix agent instance""" 17 | 18 | async def asyncSetUp(self): 19 | self.host = '127.0.0.1' 20 | self.port = 10050 21 | self.agent = AsyncGetter( 22 | host=self.host, 23 | port=self.port 24 | ) 25 | 26 | async def test_get(self): 27 | """Tests getting item values from Zabbix agent works properly""" 28 | 29 | resp = await self.agent.get('net.if.discovery') 30 | 31 | self.assertIsNotNone(resp, "Getting item values was going wrong") 32 | try: 33 | resp_list = json.loads(resp.value) 34 | except json.decoder.JSONDecodeError: 35 | self.fail(f"raised unexpected Exception while parsing response: {resp}") 36 | 37 | self.assertEqual(type(resp_list), list, "Getting item values was going wrong") 38 | for resp in resp_list: 39 | self.assertEqual(type(resp), dict, "Getting item values was going wrong") 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /.github/scripts/integration_aiosender_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import unittest 9 | 10 | sys.path.append('.') 11 | from zabbix_utils.aiosender import AsyncSender 12 | from zabbix_utils.types import ItemValue, TrapperResponse, Node 13 | 14 | 15 | class IntegrationSenderTest(unittest.IsolatedAsyncioTestCase): 16 | """Test working with a real Zabbix server/proxy instance""" 17 | 18 | async def asyncSetUp(self): 19 | self.ip = '127.0.0.1' 20 | self.port = 10051 21 | self.chunk_size = 10 22 | self.sender = AsyncSender( 23 | server=self.ip, 24 | port=self.port, 25 | chunk_size=self.chunk_size 26 | ) 27 | 28 | async def test_send(self): 29 | """Tests sending item values works properly""" 30 | 31 | items = [ 32 | ItemValue('host1', 'item.key1', 10), 33 | ItemValue('host1', 'item.key2', 'test message'), 34 | ItemValue('host2', 'item.key1', -1, 1695713666), 35 | ItemValue('host3', 'item.key1', '{"msg":"test message"}'), 36 | ItemValue('host2', 'item.key1', 0, 1695713666, 100) 37 | ] 38 | response = await self.sender.send(items) 39 | 40 | self.assertEqual(type(response.details), dict, "Sending item values was going wrong") 41 | for node, resp in response.details.items(): 42 | self.assertEqual(type(node), Node, "Sending item values was going wrong") 43 | for item in resp: 44 | self.assertEqual(type(item), TrapperResponse, "Sending item values was going wrong") 45 | for key in ('processed', 'failed', 'total', 'time', 'chunk'): 46 | try: 47 | self.assertIsNotNone(getattr(item, key), f"There aren't expected '{key}' value") 48 | except AttributeError: 49 | self.fail(f"raised unexpected Exception for attribute: {key}") 50 | 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /.github/scripts/integration_api_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import unittest 9 | 10 | sys.path.append('.') 11 | from zabbix_utils.api import ZabbixAPI 12 | from zabbix_utils.types import APIVersion 13 | 14 | 15 | class IntegrationAPITest(unittest.TestCase): 16 | """Test working with a real Zabbix API instance""" 17 | 18 | def setUp(self): 19 | self.url = 'localhost' 20 | self.user = 'Admin' 21 | self.password = 'zabbix' 22 | self.zapi = ZabbixAPI( 23 | url=self.url, 24 | user=self.user, 25 | password=self.password, 26 | skip_version_check=True 27 | ) 28 | 29 | def tearDown(self): 30 | if self.zapi: 31 | self.zapi.logout() 32 | 33 | def test_login(self): 34 | """Tests login function works properly""" 35 | 36 | self.assertEqual( 37 | type(self.zapi), ZabbixAPI, "Login was going wrong") 38 | self.assertEqual( 39 | type(self.zapi.api_version()), APIVersion, "Version getting was going wrong") 40 | 41 | def test_version_get(self): 42 | """Tests getting version info works properly""" 43 | 44 | version = None 45 | if self.zapi: 46 | version = self.zapi.apiinfo.version() 47 | self.assertEqual( 48 | version, str(self.zapi.api_version()), "Request apiinfo.version was going wrong") 49 | 50 | def test_check_auth(self): 51 | """Tests checking authentication state works properly""" 52 | 53 | resp = None 54 | if self.zapi: 55 | if self.zapi._ZabbixAPI__session_id == self.zapi._ZabbixAPI__token: 56 | resp = self.zapi.user.checkAuthentication(token=self.zapi._ZabbixAPI__session_id) 57 | else: 58 | resp = self.zapi.user.checkAuthentication(sessionid=self.zapi._ZabbixAPI__session_id) 59 | self.assertEqual( 60 | type(resp), dict, "Request user.checkAuthentication was going wrong") 61 | 62 | def test_user_get(self): 63 | """Tests getting users info works properly""" 64 | 65 | users = None 66 | if self.zapi: 67 | users = self.zapi.user.get( 68 | output=['userid', 'name'] 69 | ) 70 | self.assertEqual(type(users), list, "Request user.get was going wrong") 71 | 72 | def test_host_get(self): 73 | """Tests getting hosts info works properly using suffix""" 74 | 75 | hosts = None 76 | if self.zapi: 77 | hosts = self.zapi.host_.get_( 78 | output=['hostid', 'host'] 79 | ) 80 | self.assertEqual(type(hosts), list, "Request host.get was going wrong") 81 | 82 | 83 | if __name__ == '__main__': 84 | unittest.main() 85 | -------------------------------------------------------------------------------- /.github/scripts/integration_getter_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import json 9 | import unittest 10 | 11 | sys.path.append('.') 12 | from zabbix_utils.getter import Getter 13 | 14 | 15 | class IntegrationGetTest(unittest.TestCase): 16 | """Test working with a real Zabbix agent instance""" 17 | 18 | def setUp(self): 19 | self.host = '127.0.0.1' 20 | self.port = 10050 21 | self.agent = Getter( 22 | host=self.host, 23 | port=self.port 24 | ) 25 | 26 | def test_get(self): 27 | """Tests getting item values from Zabbix agent works properly""" 28 | 29 | resp = self.agent.get('net.if.discovery') 30 | 31 | self.assertIsNotNone(resp, "Getting item values was going wrong") 32 | try: 33 | resp_list = json.loads(resp.value) 34 | except json.decoder.JSONDecodeError: 35 | self.fail(f"raised unexpected Exception while parsing response: {resp}") 36 | 37 | self.assertEqual(type(resp_list), list, "Getting item values was going wrong") 38 | for resp in resp_list: 39 | self.assertEqual(type(resp), dict, "Getting item values was going wrong") 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /.github/scripts/integration_sender_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import unittest 9 | 10 | sys.path.append('.') 11 | from zabbix_utils.sender import Sender 12 | from zabbix_utils.types import ItemValue, TrapperResponse, Node 13 | 14 | 15 | class IntegrationSenderTest(unittest.TestCase): 16 | """Test working with a real Zabbix server/proxy instance""" 17 | 18 | def setUp(self): 19 | self.ip = '127.0.0.1' 20 | self.port = 10051 21 | self.chunk_size = 10 22 | self.sender = Sender( 23 | server=self.ip, 24 | port=self.port, 25 | chunk_size=self.chunk_size 26 | ) 27 | 28 | def test_send(self): 29 | """Tests sending item values works properly""" 30 | 31 | items = [ 32 | ItemValue('host1', 'item.key1', 10), 33 | ItemValue('host1', 'item.key2', 'test message'), 34 | ItemValue('host2', 'item.key1', -1, 1695713666), 35 | ItemValue('host3', 'item.key1', '{"msg":"test message"}'), 36 | ItemValue('host2', 'item.key1', 0, 1695713666, 100) 37 | ] 38 | response = self.sender.send(items) 39 | 40 | self.assertEqual(type(response.details), dict, "Sending item values was going wrong") 41 | for node, resp in response.details.items(): 42 | self.assertEqual(type(node), Node, "Sending item values was going wrong") 43 | for item in resp: 44 | self.assertEqual(type(item), TrapperResponse, "Sending item values was going wrong") 45 | for key in ('processed', 'failed', 'total', 'time', 'chunk'): 46 | try: 47 | self.assertIsNotNone(getattr(item, key), f"There aren't expected '{key}' value") 48 | except AttributeError: 49 | self.fail(f"raised unexpected Exception for attribute: {key}") 50 | 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /.github/scripts/library_import_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mode=$1 4 | class=$2 5 | error=$3 6 | 7 | cmd="import sys; sys.path.append('.'); from zabbix_utils import $class; $class()" 8 | if [ $mode == "async" ]; then 9 | cmd="import sys; import asyncio; sys.path.append('.'); from zabbix_utils import $class; exec('async def main():\n $class()'); asyncio.run(main())" 10 | fi 11 | 12 | result=$(python3 -c "$cmd" 2>&1) 13 | echo "$result" | grep "$error" >/dev/null || echo "$result" | (python3 "./.github/scripts/telegram_msg.py" && echo "Error") 14 | -------------------------------------------------------------------------------- /.github/scripts/release_notification.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | import os 5 | import json 6 | import smtplib 7 | import markdown 8 | 9 | from email.mime.text import MIMEText 10 | from email.mime.multipart import MIMEMultipart 11 | 12 | # Repository constants 13 | LIBRARY_VERSION = os.environ['LIBRARY_VERSION'] 14 | REPOSITORY = os.environ['REPOSITORY'] 15 | 16 | # Mail server variables 17 | mail_port = int(os.environ.get('MAIL_PORT', '465')) 18 | mail_server = os.environ['MAIL_SERVER'] 19 | auth_user = os.environ['MAIL_USER'] 20 | auth_pass = os.environ['MAIL_PASS'] 21 | 22 | # Mail variables 23 | mail_from = '"zabbix_utils" <' + auth_user + '>' 24 | mail_to = json.loads(os.environ['RELEASE_RECIPIENT_LIST']) 25 | mail_subject = f"[GitHub] A new version {LIBRARY_VERSION} of the zabbix_utils library has been released" 26 | mail_text = f""" 27 | A new version of the zabbix_utils library has been released: 28 | v{LIBRARY_VERSION} 29 | 30 |

31 | """ 32 | 33 | # Reading release notes 34 | with open("RELEASE_NOTES.md", "r", encoding="utf-8") as fh: 35 | release_notes = markdown.markdown("\n".join(fh.readlines()[1:])) 36 | 37 | # Preparing mail data 38 | msg = MIMEMultipart('mixed') 39 | msg['Subject'] = mail_subject 40 | msg['From'] = mail_from 41 | msg['To'] = ', '.join(mail_to) 42 | 43 | # Adding message text 44 | msg.attach(MIMEText(mail_text + release_notes, 'html')) 45 | 46 | # Connection to the mail server 47 | server = smtplib.SMTP_SSL(mail_server, mail_port) 48 | server.login(auth_user, auth_pass) 49 | 50 | # Sending email 51 | server.sendmail(mail_from, mail_to, msg.as_string()) 52 | 53 | # Closing connection 54 | server.quit() 55 | -------------------------------------------------------------------------------- /.github/scripts/telegram_msg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import os 8 | import sys 9 | import json 10 | import requests 11 | 12 | chat_id = os.environ.get("TBOT_CHAT") # chat id. env TBOT_CHAT must be set! 13 | token = os.environ.get("TBOT_TOKEN") # bot token. env TBOT_TOKEN must be set! 14 | parse_mode = os.environ.get("TBOT_FORMAT", '') # HTML, MarkdownV2 or empty 15 | 16 | for key in ["TBOT_CHAT", "TBOT_TOKEN"]: 17 | if not os.environ.get(key): 18 | print(f"Please set environmental variable \"{key}\"") 19 | sys.exit(1) 20 | 21 | 22 | def sendMessage(msg, passthrough=True): 23 | 24 | if passthrough: 25 | print(msg) 26 | 27 | if len(msg) == 0: 28 | return '{"ok":true}' 29 | 30 | url = f"https://api.telegram.org/bot{token}/sendMessage" 31 | 32 | if len(msg) > 4096: 33 | msg = "Message output is too long. Please check the GitHub action log." 34 | 35 | if os.environ.get("SUBJECT"): 36 | msg = f'{os.environ.get("SUBJECT")}\n\n{msg}' 37 | 38 | if os.environ.get("GH_JOB"): 39 | msg += f'\n\n{os.environ.get("GH_JOB")}' 40 | 41 | payload = { 42 | "text": msg, 43 | "parse_mode": parse_mode, 44 | "disable_web_page_preview": False, 45 | "disable_notification": False, 46 | "reply_to_message_id": None, 47 | "chat_id": chat_id 48 | } 49 | headers = { 50 | "accept": "application/json", 51 | "User-Agent": "Python script", 52 | "content-type": "application/json" 53 | } 54 | 55 | response = requests.post(url, json=payload, headers=headers) 56 | 57 | return response.text 58 | 59 | 60 | if len(sys.argv) == 2: 61 | message = sys.argv[1] 62 | else: 63 | message = sys.stdin.read() 64 | 65 | if not message: 66 | sys.exit(0) 67 | 68 | result = json.loads(sendMessage(message)) 69 | if not result["ok"]: 70 | print(result["error_code"], result["description"]) 71 | sys.exit(1) 72 | -------------------------------------------------------------------------------- /.github/scripts/wait_instance_zabbix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (C) 2001-2023 Zabbix SIA 3 | # 4 | # Zabbix SIA licenses this file under the MIT License. 5 | # See the LICENSE file in the project root for more information. 6 | 7 | import sys 8 | import time 9 | 10 | sys.path.append('.') 11 | from zabbix_utils import ZabbixAPI, APIRequestError 12 | 13 | for x in range(20): 14 | try: 15 | zapi = ZabbixAPI( 16 | url="localhost", 17 | user="Admin", 18 | password="zabbix", 19 | skip_version_check=True 20 | ) 21 | except APIRequestError as error: 22 | print(f'Zabbix API is not ready... Data: {error}', flush=True) 23 | time.sleep(5) 24 | else: 25 | zapi.logout() 26 | sys.exit(0) 27 | sys.exit('Failed to wait for Zabbix API to be ready') 28 | -------------------------------------------------------------------------------- /.github/workflows/additional_tests.yaml: -------------------------------------------------------------------------------- 1 | name: additional_tests 2 | run-name: Additional tests for API features 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | env: 12 | ZABBIX_BRANCH: master 13 | CONFIG_PATH: .github/configs/ 14 | TEST_FILE: additional_api_tests.py 15 | 16 | jobs: 17 | importing-tests: 18 | name: Importing tests 19 | runs-on: ubuntu-22.04 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Install Python 24 | run: | 25 | sudo apt update && sudo apt-get install -y python3 python3-pip python-is-python3 26 | - name: Prepare environment 27 | run: | 28 | touch /tmp/importing.log 29 | - name: Check import of sync without requirements 30 | continue-on-error: true 31 | env: 32 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 33 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 34 | SUBJECT: Importing test without requirements FAIL 35 | run: | 36 | bash ./.github/scripts/library_import_tests.sh "sync" "ZabbixAPI" "Unable to connect to" > /tmp/importing.log 37 | - name: Check import of async without requirements 38 | continue-on-error: true 39 | env: 40 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 41 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 42 | SUBJECT: Importing test without requirements FAIL 43 | run: | 44 | bash ./.github/scripts/library_import_tests.sh "async" "AsyncZabbixAPI" "ModuleNotFoundError:" > /tmp/importing.log 45 | - name: Install requirements 46 | run: | 47 | pip install -r ./requirements.txt 48 | - name: Check import of async with requirements 49 | continue-on-error: true 50 | env: 51 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 52 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 53 | SUBJECT: Importing tests with requirements FAIL 54 | run: | 55 | bash ./.github/scripts/library_import_tests.sh "async" "AsyncZabbixAPI" "Unable to connect to" > /tmp/importing.log 56 | - name: Raise an exception 57 | run: | 58 | test $(cat /tmp/importing.log | wc -l) -eq 0 || exit 1 59 | additional-tests: 60 | name: Additional tests 61 | runs-on: ubuntu-latest 62 | 63 | steps: 64 | - uses: actions/checkout@v4 65 | - name: Install packages 66 | run: | 67 | sudo apt update && sudo apt install -y git sudo nginx 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 68 | - name: Build from sources 69 | run: | 70 | WORKDIR=$(pwd) 71 | cd /tmp/ 72 | 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 73 | cd /tmp/zabbix-branch 74 | ./bootstrap.sh 75 | ./configure --enable-server --with-postgresql 76 | sudo make dbschema_postgresql 77 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 78 | sudo mkdir -p /etc/nginx/ssl/ 79 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/.htpasswd /etc/nginx/.htpasswd 80 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/default.conf /etc/nginx/sites-enabled/default 81 | 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 82 | sudo chown -R www-data:www-data /etc/nginx/ 83 | cd ui 84 | sudo rm /var/www/html/index.html 85 | sudo cp -a . /var/www/html/ 86 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 87 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf 88 | sudo chown -R www-data:www-data /var/www/html/ 89 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.1/apache2/php.ini 90 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.1/apache2/php.ini 91 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.1/apache2/php.ini 92 | sudo sed -i "s/Listen 80/Listen 8080/g" /etc/apache2/ports.conf 93 | sudo sed -i "s///g" /etc/apache2/sites-enabled/000-default.conf 94 | sudo locale-gen en_US.UTF-8 95 | sudo update-locale 96 | - name: Prepare environment 97 | run: | 98 | sudo addgroup --system --quiet zabbix 99 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 100 | sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp 101 | sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 102 | sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 103 | (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& 104 | sleep 5 105 | cd /tmp/zabbix-branch/database/postgresql 106 | sudo -u postgres createuser zabbix 107 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 108 | cat schema.sql | sudo -u zabbix psql zabbix 109 | cat images.sql | sudo -u zabbix psql zabbix 110 | cat data.sql | sudo -u zabbix psql zabbix 111 | - name: Start Apache & Nginx 112 | run: | 113 | sudo apache2ctl start 114 | sudo nginx -g "daemon on; master_process on;" 115 | - name: Install python3 116 | run: | 117 | sudo apt-get install -y python3 python3-pip python-is-python3 118 | pip install -r ./requirements.txt 119 | - name: Run tests 120 | continue-on-error: true 121 | run: | 122 | sleep 5 123 | python ./.github/scripts/$TEST_FILE 2>/tmp/additional.log >/dev/null 124 | - name: Send report 125 | env: 126 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 127 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 128 | SUBJECT: Zabbix API additional tests FAIL 129 | run: | 130 | tail -n1 /tmp/additional.log | grep "OK" 1>/dev/null || tail /tmp/additional.log | python ./.github/scripts/telegram_msg.py | exit 1 131 | -------------------------------------------------------------------------------- /.github/workflows/check_new_release.yaml: -------------------------------------------------------------------------------- 1 | name: check_new_release 2 | run-name: Check new Zabbix release 3 | 4 | on: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | check-release: 11 | name: Check Zabbix release 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Prepare environment 17 | run: | 18 | sudo apt-get install -y python3 python3-pip python-is-python3 19 | pip install -r ./requirements.txt 20 | - name: Check new Zabbix release 21 | env: 22 | BRANCHES_URL: ${{ vars.BRANCHES_URL }} 23 | LIBREPO_URL: ${{ vars.LIBREPO_URL }} 24 | MANUAL_REPO: ${{ vars.MANUAL_REPO }} 25 | run: | 26 | python ./.github/scripts/check_new_zabbx_release.py 2>/tmp/check_release.log || echo 27 | - name: Send report 28 | env: 29 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 30 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 31 | TBOT_FORMAT: html 32 | SUBJECT: zabbix_utils repo requires update due new Zabbix release 33 | run: | 34 | tail /tmp/check_release.log | python ./.github/scripts/telegram_msg.py 35 | -------------------------------------------------------------------------------- /.github/workflows/compatibility_50.yaml: -------------------------------------------------------------------------------- 1 | name: zabbix_50 2 | run-name: Compatibility with Zabbix 5.0 test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | env: 12 | ZABBIX_VERSION: '5.0' 13 | ZABBIX_BRANCH: release/$ZABBIX_VERSION 14 | CONFIG_PATH: .github/configs/ 15 | TEST_FILE: compatibility_api_test_5.py 16 | 17 | jobs: 18 | compatibility: 19 | name: Compatibility test 20 | runs-on: ubuntu-20.04 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Install packages 25 | run: | 26 | curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg 27 | echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | sudo tee /etc/apt/sources.list.d/pgdg.list 28 | sudo add-apt-repository ppa:ondrej/php 29 | 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 libzip-dev php7.4 libapache2-mod-php7.4 php7.4-common php7.4-fpm php7.4-gd php7.4-mbstring php7.4-xml php7.4-zip php7.4-ldap 30 | - name: Build from sources 31 | run: | 32 | WORKDIR=$(pwd) 33 | cd /tmp/ 34 | 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 35 | cd /tmp/zabbix-branch 36 | ./bootstrap.sh 37 | ./configure --enable-server --enable-agent --with-postgresql 38 | sudo make dbschema_postgresql 39 | sudo make 40 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 41 | cd ui 42 | sudo rm /var/www/html/index.html 43 | sudo cp -a . /var/www/html/ 44 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 45 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf 46 | sudo chown -R www-data:www-data /var/www/html/ 47 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/7.4/fpm/php.ini 48 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/7.4/fpm/php.ini 49 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/7.4/fpm/php.ini 50 | sudo locale-gen en_US.UTF-8 51 | sudo update-locale 52 | - name: Prepare environment 53 | run: | 54 | sudo addgroup --system --quiet zabbix 55 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 56 | sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp 57 | sudo chown -R postgres. /var/run/postgresql/ 58 | sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 59 | sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 60 | sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/14/main/postgresql.conf 61 | (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& 62 | sleep 5 63 | cd /tmp/zabbix-branch/database/postgresql 64 | sudo -u postgres createuser zabbix 65 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 66 | cat schema.sql | sudo -u zabbix psql zabbix 67 | cat images.sql | sudo -u zabbix psql zabbix 68 | cat data.sql | sudo -u zabbix psql zabbix 69 | sudo apache2ctl start 70 | - name: Start Zabbix server 71 | run: | 72 | cd /tmp/zabbix-branch 73 | sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf 74 | - name: Start Zabbix agent 75 | run: | 76 | cd /tmp/zabbix-branch 77 | sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf 78 | - name: Install python3 79 | run: | 80 | sudo apt-get install -y python3 python3-pip python-is-python3 81 | pip install -r ./requirements.txt 82 | - name: Wait for Zabbix API 83 | run: | 84 | python ./.github/scripts/wait_instance_zabbix.py 85 | - name: Compatibility test 86 | continue-on-error: true 87 | run: | 88 | python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null 89 | - name: Send report 90 | env: 91 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 92 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 93 | SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL 94 | run: | 95 | tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 96 | -------------------------------------------------------------------------------- /.github/workflows/compatibility_60.yaml: -------------------------------------------------------------------------------- 1 | name: zabbix_60 2 | run-name: Compatibility with Zabbix 6.0 test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | env: 12 | ZABBIX_VERSION: '6.0' 13 | ZABBIX_BRANCH: release/$ZABBIX_VERSION 14 | CONFIG_PATH: .github/configs/ 15 | TEST_FILE: compatibility_api_test_6.py 16 | 17 | jobs: 18 | compatibility: 19 | name: Compatibility test 20 | runs-on: ubuntu-22.04 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Install packages 25 | run: | 26 | 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 27 | - name: Build from sources 28 | run: | 29 | WORKDIR=$(pwd) 30 | cd /tmp/ 31 | 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 32 | cd /tmp/zabbix-branch 33 | ./bootstrap.sh 34 | ./configure --enable-server --enable-agent --with-postgresql 35 | sudo make dbschema_postgresql 36 | sudo make 37 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 38 | cd ui 39 | sudo rm /var/www/html/index.html 40 | sudo cp -a . /var/www/html/ 41 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 42 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf 43 | sudo chown -R www-data:www-data /var/www/html/ 44 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.1/apache2/php.ini 45 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.1/apache2/php.ini 46 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.1/apache2/php.ini 47 | sudo locale-gen en_US.UTF-8 48 | sudo update-locale 49 | - name: Prepare environment 50 | run: | 51 | sudo addgroup --system --quiet zabbix 52 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 53 | sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp 54 | sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 55 | sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 56 | (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& 57 | sleep 5 58 | cd /tmp/zabbix-branch/database/postgresql 59 | sudo -u postgres createuser zabbix 60 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 61 | cat schema.sql | sudo -u zabbix psql zabbix 62 | cat images.sql | sudo -u zabbix psql zabbix 63 | cat data.sql | sudo -u zabbix psql zabbix 64 | sudo apache2ctl start 65 | - name: Start Zabbix server 66 | run: | 67 | cd /tmp/zabbix-branch 68 | sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf 69 | - name: Start Zabbix agent 70 | run: | 71 | cd /tmp/zabbix-branch 72 | sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf 73 | - name: Install python3 74 | run: | 75 | sudo apt-get install -y python3 python3-pip python-is-python3 76 | pip install -r ./requirements.txt 77 | - name: Wait for Zabbix API 78 | run: | 79 | python ./.github/scripts/wait_instance_zabbix.py 80 | - name: Compatibility test 81 | continue-on-error: true 82 | run: | 83 | python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null 84 | - name: Send report 85 | env: 86 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 87 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 88 | SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL 89 | run: | 90 | tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 91 | -------------------------------------------------------------------------------- /.github/workflows/compatibility_70.yaml: -------------------------------------------------------------------------------- 1 | name: zabbix_70 2 | run-name: Compatibility with Zabbix 7.0 test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | env: 12 | ZABBIX_VERSION: '7.0' 13 | ZABBIX_BRANCH: release/$ZABBIX_VERSION 14 | CONFIG_PATH: .github/configs/ 15 | TEST_FILE: compatibility_api_test_7.py 16 | 17 | jobs: 18 | compatibility: 19 | name: Compatibility test 20 | runs-on: ubuntu-24.04 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install packages 25 | run: | 26 | sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-16 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.3-pgsql php8.3-bcmath php8.3-xml php8.3-gd php8.3-ldap php8.3-mbstring libzip-dev 27 | - name: Build from sources 28 | run: | 29 | WORKDIR=$(pwd) 30 | cd /tmp/ 31 | sudo addgroup --system --quiet zabbix 32 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 33 | 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 34 | cd /tmp/zabbix-branch 35 | ./bootstrap.sh 36 | ./configure --enable-server --enable-agent --with-postgresql 37 | sudo make dbschema_postgresql 38 | sudo make 39 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 40 | ./configure --enable-proxy --with-sqlite3 41 | sudo make 42 | mkdir /tmp/zabbix_proxy1/ 43 | mkdir /tmp/zabbix_proxy2/ 44 | cp ./conf/zabbix_proxy.conf ./conf/zabbix_proxy1.conf 45 | mv ./conf/zabbix_proxy.conf ./conf/zabbix_proxy2.conf 46 | sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy1/g" ./conf/zabbix_proxy1.conf 47 | sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy2/g" ./conf/zabbix_proxy2.conf 48 | sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy1.log#g" ./conf/zabbix_proxy1.conf 49 | sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy2.log#g" ./conf/zabbix_proxy2.conf 50 | sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy1.db#' ./conf/zabbix_proxy1.conf 51 | sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy2.db#' ./conf/zabbix_proxy2.conf 52 | echo -e "PidFile=/tmp/zabbix_proxy1/zabbix_proxy1.pid\n" >> ./conf/zabbix_proxy1.conf 53 | echo -e "PidFile=/tmp/zabbix_proxy2/zabbix_proxy2.pid\n" >> ./conf/zabbix_proxy2.conf 54 | echo -e "SocketDir=/tmp/zabbix_proxy1\n" >> ./conf/zabbix_proxy1.conf 55 | echo -e "SocketDir=/tmp/zabbix_proxy2\n" >> ./conf/zabbix_proxy2.conf 56 | echo -e "ListenPort=10061\n" >> ./conf/zabbix_proxy1.conf 57 | echo -e "ListenPort=10062\n" >> ./conf/zabbix_proxy2.conf 58 | sudo chown -R zabbix:zabbix /tmp/zabbix_proxy1/ 59 | sudo chown -R zabbix:zabbix /tmp/zabbix_proxy2/ 60 | cd ui 61 | sudo rm /var/www/html/index.html 62 | sudo cp -a . /var/www/html/ 63 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 64 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/16/main/pg_hba.conf 65 | sudo chown -R www-data:www-data /var/www/html/ 66 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.3/apache2/php.ini 67 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.3/apache2/php.ini 68 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.3/apache2/php.ini 69 | sudo locale-gen en_US.UTF-8 70 | sudo update-locale 71 | - name: Prepare environment 72 | run: | 73 | sudo mkdir -p /var/run/postgresql/16-main.pg_stat_tmp 74 | sudo touch /var/run/postgresql/16-main.pg_stat_tmp/global.tmp 75 | sudo chmod 0777 /var/run/postgresql/16-main.pg_stat_tmp/global.tmp 76 | (sudo -u postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main -c config_file=/etc/postgresql/16/main/postgresql.conf)& 77 | sleep 5 78 | cd /tmp/zabbix-branch/database/postgresql 79 | sudo -u postgres createuser zabbix 80 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 81 | cat schema.sql | sudo -u zabbix psql zabbix 82 | cat images.sql | sudo -u zabbix psql zabbix 83 | cat data.sql | sudo -u zabbix psql zabbix 84 | sudo apache2ctl start 85 | - name: Start Zabbix server 86 | run: | 87 | cd /tmp/zabbix-branch 88 | sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf 89 | - name: Start Zabbix proxies 90 | continue-on-error: true 91 | run: | 92 | cd /tmp/zabbix-branch 93 | sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy1.conf 94 | sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy2.conf 95 | - name: Start Zabbix agent 96 | run: | 97 | cd /tmp/zabbix-branch 98 | sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf 99 | - name: Install python3 100 | run: | 101 | sudo apt-get install -y python3 python3-pip python-is-python3 102 | pip install -r ./requirements.txt 103 | - name: Wait for Zabbix API 104 | run: | 105 | python ./.github/scripts/wait_instance_zabbix.py 106 | - name: Compatibility test 107 | continue-on-error: true 108 | run: | 109 | python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null 110 | - name: Send report 111 | env: 112 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 113 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 114 | SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL 115 | run: | 116 | tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 117 | -------------------------------------------------------------------------------- /.github/workflows/compatibility_72.yaml: -------------------------------------------------------------------------------- 1 | name: zabbix_72 2 | run-name: Compatibility with Zabbix 7.2 test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | env: 12 | ZABBIX_VERSION: '7.2' 13 | ZABBIX_BRANCH: release/$ZABBIX_VERSION 14 | CONFIG_PATH: .github/configs/ 15 | TEST_FILE: compatibility_api_test_7.py 16 | 17 | jobs: 18 | compatibility: 19 | name: Compatibility test 20 | runs-on: ubuntu-24.04 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install packages 25 | run: | 26 | sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-16 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.3-pgsql php8.3-bcmath php8.3-xml php8.3-gd php8.3-ldap php8.3-mbstring libzip-dev 27 | - name: Build from sources 28 | run: | 29 | WORKDIR=$(pwd) 30 | cd /tmp/ 31 | sudo addgroup --system --quiet zabbix 32 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 33 | 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 34 | cd /tmp/zabbix-branch 35 | ./bootstrap.sh 36 | ./configure --enable-server --enable-agent --with-postgresql 37 | sudo make dbschema_postgresql 38 | sudo make 39 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 40 | ./configure --enable-proxy --with-sqlite3 41 | sudo make 42 | mkdir /tmp/zabbix_proxy1/ 43 | mkdir /tmp/zabbix_proxy2/ 44 | cp ./conf/zabbix_proxy.conf ./conf/zabbix_proxy1.conf 45 | mv ./conf/zabbix_proxy.conf ./conf/zabbix_proxy2.conf 46 | sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy1/g" ./conf/zabbix_proxy1.conf 47 | sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy2/g" ./conf/zabbix_proxy2.conf 48 | sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy1.log#g" ./conf/zabbix_proxy1.conf 49 | sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy2.log#g" ./conf/zabbix_proxy2.conf 50 | sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy1.db#' ./conf/zabbix_proxy1.conf 51 | sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy2.db#' ./conf/zabbix_proxy2.conf 52 | echo -e "PidFile=/tmp/zabbix_proxy1/zabbix_proxy1.pid\n" >> ./conf/zabbix_proxy1.conf 53 | echo -e "PidFile=/tmp/zabbix_proxy2/zabbix_proxy2.pid\n" >> ./conf/zabbix_proxy2.conf 54 | echo -e "SocketDir=/tmp/zabbix_proxy1\n" >> ./conf/zabbix_proxy1.conf 55 | echo -e "SocketDir=/tmp/zabbix_proxy2\n" >> ./conf/zabbix_proxy2.conf 56 | echo -e "ListenPort=10061\n" >> ./conf/zabbix_proxy1.conf 57 | echo -e "ListenPort=10062\n" >> ./conf/zabbix_proxy2.conf 58 | sudo chown -R zabbix:zabbix /tmp/zabbix_proxy1/ 59 | sudo chown -R zabbix:zabbix /tmp/zabbix_proxy2/ 60 | cd ui 61 | sudo rm /var/www/html/index.html 62 | sudo cp -a . /var/www/html/ 63 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 64 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/16/main/pg_hba.conf 65 | sudo chown -R www-data:www-data /var/www/html/ 66 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.3/apache2/php.ini 67 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.3/apache2/php.ini 68 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.3/apache2/php.ini 69 | sudo locale-gen en_US.UTF-8 70 | sudo update-locale 71 | - name: Prepare environment 72 | run: | 73 | sudo mkdir -p /var/run/postgresql/16-main.pg_stat_tmp 74 | sudo touch /var/run/postgresql/16-main.pg_stat_tmp/global.tmp 75 | sudo chmod 0777 /var/run/postgresql/16-main.pg_stat_tmp/global.tmp 76 | (sudo -u postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main -c config_file=/etc/postgresql/16/main/postgresql.conf)& 77 | sleep 5 78 | cd /tmp/zabbix-branch/database/postgresql 79 | sudo -u postgres createuser zabbix 80 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 81 | cat schema.sql | sudo -u zabbix psql zabbix 82 | cat images.sql | sudo -u zabbix psql zabbix 83 | cat data.sql | sudo -u zabbix psql zabbix 84 | sudo apache2ctl start 85 | - name: Start Zabbix server 86 | run: | 87 | cd /tmp/zabbix-branch 88 | sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf 89 | - name: Start Zabbix proxies 90 | continue-on-error: true 91 | run: | 92 | cd /tmp/zabbix-branch 93 | sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy1.conf 94 | sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy2.conf 95 | - name: Start Zabbix agent 96 | run: | 97 | cd /tmp/zabbix-branch 98 | sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf 99 | - name: Install python3 100 | run: | 101 | sudo apt-get install -y python3 python3-pip python-is-python3 102 | pip install -r ./requirements.txt 103 | - name: Wait for Zabbix API 104 | run: | 105 | python ./.github/scripts/wait_instance_zabbix.py 106 | - name: Compatibility test 107 | continue-on-error: true 108 | run: | 109 | python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null 110 | - name: Send report 111 | env: 112 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 113 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 114 | SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL 115 | run: | 116 | tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 117 | -------------------------------------------------------------------------------- /.github/workflows/compatibility_latest.yaml: -------------------------------------------------------------------------------- 1 | name: zabbix_latest 2 | run-name: Compatibility with the latest Zabbix version test 3 | on: 4 | schedule: 5 | - cron: "0 1 * * *" 6 | workflow_dispatch: 7 | 8 | env: 9 | ZABBIX_VERSION: 'latest' 10 | ZABBIX_BRANCH: master 11 | CONFIG_PATH: .github/configs/ 12 | TEST_FILE: compatibility_api_test_latest.py 13 | 14 | jobs: 15 | compatibility: 16 | name: Compatibility test 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Install packages 22 | run: | 23 | sudo apt update && sudo apt install -y git sudo gcc make automake pkg-config postgresql-16 libpostgresql-ocaml-dev libxml2-dev libpcre3-dev libevent-dev apache2 libapache2-mod-php php8.3-pgsql php8.3-bcmath php8.3-xml php8.3-gd php8.3-ldap php8.3-mbstring libzip-dev 24 | - name: Build from sources 25 | run: | 26 | WORKDIR=$(pwd) 27 | cd /tmp/ 28 | sudo addgroup --system --quiet zabbix 29 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 30 | 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 31 | cd /tmp/zabbix-branch 32 | ./bootstrap.sh 33 | ./configure --enable-server --enable-agent --with-postgresql 34 | sudo make dbschema_postgresql 35 | sudo make 36 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 37 | ./configure --enable-proxy --with-sqlite3 38 | sudo make 39 | mkdir /tmp/zabbix_proxy1/ 40 | mkdir /tmp/zabbix_proxy2/ 41 | cp ./conf/zabbix_proxy.conf ./conf/zabbix_proxy1.conf 42 | mv ./conf/zabbix_proxy.conf ./conf/zabbix_proxy2.conf 43 | sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy1/g" ./conf/zabbix_proxy1.conf 44 | sed -i "s/Hostname=Zabbix proxy/Hostname=CompatibilitySenderTest_proxy2/g" ./conf/zabbix_proxy2.conf 45 | sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy1.log#g" ./conf/zabbix_proxy1.conf 46 | sed -i "s#LogFile=/tmp/zabbix_proxy.log#LogFile=/tmp/zabbix_proxy2.log#g" ./conf/zabbix_proxy2.conf 47 | sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy1.db#' ./conf/zabbix_proxy1.conf 48 | sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy2.db#' ./conf/zabbix_proxy2.conf 49 | echo -e "PidFile=/tmp/zabbix_proxy1/zabbix_proxy1.pid\n" >> ./conf/zabbix_proxy1.conf 50 | echo -e "PidFile=/tmp/zabbix_proxy2/zabbix_proxy2.pid\n" >> ./conf/zabbix_proxy2.conf 51 | echo -e "SocketDir=/tmp/zabbix_proxy1\n" >> ./conf/zabbix_proxy1.conf 52 | echo -e "SocketDir=/tmp/zabbix_proxy2\n" >> ./conf/zabbix_proxy2.conf 53 | echo -e "ListenPort=10061\n" >> ./conf/zabbix_proxy1.conf 54 | echo -e "ListenPort=10062\n" >> ./conf/zabbix_proxy2.conf 55 | sudo chown -R zabbix:zabbix /tmp/zabbix_proxy1/ 56 | sudo chown -R zabbix:zabbix /tmp/zabbix_proxy2/ 57 | cd ui 58 | sudo rm /var/www/html/index.html 59 | sudo cp -a . /var/www/html/ 60 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 61 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/16/main/pg_hba.conf 62 | sudo chown -R www-data:www-data /var/www/html/ 63 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.3/apache2/php.ini 64 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.3/apache2/php.ini 65 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.3/apache2/php.ini 66 | sudo locale-gen en_US.UTF-8 67 | sudo update-locale 68 | - name: Prepare environment 69 | run: | 70 | sudo mkdir -p /var/run/postgresql/16-main.pg_stat_tmp 71 | sudo touch /var/run/postgresql/16-main.pg_stat_tmp/global.tmp 72 | sudo chmod 0777 /var/run/postgresql/16-main.pg_stat_tmp/global.tmp 73 | (sudo -u postgres /usr/lib/postgresql/16/bin/postgres -D /var/lib/postgresql/16/main -c config_file=/etc/postgresql/16/main/postgresql.conf)& 74 | sleep 5 75 | cd /tmp/zabbix-branch/database/postgresql 76 | sudo -u postgres createuser zabbix 77 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 78 | cat schema.sql | sudo -u zabbix psql zabbix 79 | cat images.sql | sudo -u zabbix psql zabbix 80 | cat data.sql | sudo -u zabbix psql zabbix 81 | sudo apache2ctl start 82 | - name: Start Zabbix server 83 | run: | 84 | cd /tmp/zabbix-branch 85 | sudo ./src/zabbix_server/zabbix_server -c ./conf/zabbix_server.conf 86 | - name: Start Zabbix proxies 87 | continue-on-error: true 88 | run: | 89 | cd /tmp/zabbix-branch 90 | sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy1.conf 91 | sudo ./src/zabbix_proxy/zabbix_proxy -c ./conf/zabbix_proxy2.conf 92 | - name: Start Zabbix agent 93 | run: | 94 | cd /tmp/zabbix-branch 95 | sudo ./src/zabbix_agent/zabbix_agentd -c ./conf/zabbix_agentd.conf 96 | - name: Install python3 97 | run: | 98 | sudo apt-get install -y python3 python3-pip python-is-python3 99 | pip install -r ./requirements.txt 100 | - name: Wait for Zabbix API 101 | run: | 102 | python ./.github/scripts/wait_instance_zabbix.py 103 | - name: Print Zabbix version 104 | continue-on-error: true 105 | run: | 106 | grep -Po "(?<=Changes for ).*$" /tmp/zabbix-branch/ChangeLog 2>/dev/null | head -n1 107 | - name: Compatibility test 108 | continue-on-error: true 109 | run: | 110 | python ./.github/scripts/$TEST_FILE 2>/tmp/compatibility.log >/dev/null 111 | - name: Send report 112 | env: 113 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 114 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 115 | SUBJECT: Compatibility with Zabbix ${{ env.ZABBIX_VERSION }} FAIL 116 | run: | 117 | tail -n1 /tmp/compatibility.log | grep "OK" 1>/dev/null || tail /tmp/compatibility.log | python ./.github/scripts/telegram_msg.py | exit 1 118 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | run-name: Check test coverage 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | coverage: 13 | name: Check coverage 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python 3.10 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: "3.10" 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install -r ./requirements.txt 26 | pip install coverage 27 | - name: Test with coverage 28 | run: | 29 | coverage run -m unittest discover -s ./tests -p 'test_*.py' 30 | coverage report 31 | -------------------------------------------------------------------------------- /.github/workflows/depricated_tests.yaml: -------------------------------------------------------------------------------- 1 | name: depricated_tests 2 | run-name: Tests for deprecated features 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | env: 12 | ZABBIX_VERSION: '7.0' 13 | ZABBIX_BRANCH: release/$ZABBIX_VERSION 14 | CONFIG_PATH: .github/configs/ 15 | TEST_FILE: depricated_tests.py 16 | 17 | jobs: 18 | depricated-tests: 19 | name: Depricated tests 20 | runs-on: ubuntu-22.04 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Install packages 25 | run: | 26 | sudo apt update && sudo apt install -y git sudo nginx 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 27 | - name: Build from sources 28 | run: | 29 | WORKDIR=$(pwd) 30 | cd /tmp/ 31 | 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 32 | cd /tmp/zabbix-branch 33 | ./bootstrap.sh 34 | ./configure --enable-server --with-postgresql 35 | sudo make dbschema_postgresql 36 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 37 | sudo mkdir -p /etc/nginx/ssl/ 38 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/.htpasswd /etc/nginx/.htpasswd 39 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/default.conf /etc/nginx/sites-enabled/default 40 | 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 41 | sudo chown -R www-data:www-data /etc/nginx/ 42 | cd ui 43 | sudo rm /var/www/html/index.html 44 | sudo cp -a . /var/www/html/ 45 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 46 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf 47 | sudo chown -R www-data:www-data /var/www/html/ 48 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.1/apache2/php.ini 49 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.1/apache2/php.ini 50 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.1/apache2/php.ini 51 | sudo sed -i "s/Listen 80/Listen 8080/g" /etc/apache2/ports.conf 52 | sudo sed -i "s///g" /etc/apache2/sites-enabled/000-default.conf 53 | sudo locale-gen en_US.UTF-8 54 | sudo update-locale 55 | - name: Prepare environment 56 | run: | 57 | sudo addgroup --system --quiet zabbix 58 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 59 | sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp 60 | sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 61 | sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 62 | (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& 63 | sleep 5 64 | cd /tmp/zabbix-branch/database/postgresql 65 | sudo -u postgres createuser zabbix 66 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 67 | cat schema.sql | sudo -u zabbix psql zabbix 68 | cat images.sql | sudo -u zabbix psql zabbix 69 | cat data.sql | sudo -u zabbix psql zabbix 70 | - name: Start Apache & Nginx 71 | run: | 72 | sudo apache2ctl start 73 | sudo nginx -g "daemon on; master_process on;" 74 | - name: Install python3 75 | run: | 76 | sudo apt-get install -y python3 python3-pip python-is-python3 77 | pip install -r ./requirements.txt 78 | - name: Run tests 79 | continue-on-error: true 80 | run: | 81 | sleep 5 82 | python ./.github/scripts/$TEST_FILE 2>/tmp/depricated.log >/dev/null 83 | - name: Send report 84 | env: 85 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 86 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 87 | SUBJECT: Zabbix API depricated tests FAIL 88 | run: | 89 | tail -n1 /tmp/depricated.log | grep "OK" 1>/dev/null || tail /tmp/depricated.log | python ./.github/scripts/telegram_msg.py | exit 1 90 | -------------------------------------------------------------------------------- /.github/workflows/integration_api.yaml: -------------------------------------------------------------------------------- 1 | name: api 2 | run-name: Zabbix API integration test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - '**api.py' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - '**api.py' 13 | workflow_dispatch: 14 | 15 | env: 16 | ZABBIX_BRANCH: master 17 | CONFIG_PATH: .github/configs/ 18 | SYNC_FILE: integration_api_test.py 19 | ASYNC_FILE: integration_aioapi_test.py 20 | 21 | jobs: 22 | integration: 23 | name: Integration test 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Install packages 29 | run: | 30 | 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 31 | - name: Build from sources 32 | run: | 33 | WORKDIR=$(pwd) 34 | cd /tmp/ 35 | 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 36 | cd /tmp/zabbix-branch 37 | ./bootstrap.sh 38 | ./configure --enable-server --with-postgresql 39 | sudo make dbschema_postgresql 40 | echo -e "CacheUpdateFrequency=1\n" >> ./conf/zabbix_server.conf 41 | cd ui 42 | sudo rm /var/www/html/index.html 43 | sudo cp -a . /var/www/html/ 44 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/zabbix.conf.php /var/www/html/conf/ 45 | sudo cp $WORKDIR/${{ env.CONFIG_PATH }}/pg_hba.conf /etc/postgresql/14/main/pg_hba.conf 46 | sudo chown -R www-data:www-data /var/www/html/ 47 | sudo sed -i "s/post_max_size = 8M/post_max_size = 16M/g" /etc/php/8.1/apache2/php.ini 48 | sudo sed -i "s/max_execution_time = 30/max_execution_time = 300/g" /etc/php/8.1/apache2/php.ini 49 | sudo sed -i "s/max_input_time = 60/max_input_time = 300/g" /etc/php/8.1/apache2/php.ini 50 | sudo locale-gen en_US.UTF-8 51 | sudo update-locale 52 | - name: Prepare environment 53 | run: | 54 | sudo addgroup --system --quiet zabbix 55 | sudo adduser --quiet --system --disabled-login --ingroup zabbix --home /var/lib/zabbix --no-create-home zabbix 56 | sudo mkdir -p /var/run/postgresql/14-main.pg_stat_tmp 57 | sudo touch /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 58 | sudo chmod 0777 /var/run/postgresql/14-main.pg_stat_tmp/global.tmp 59 | (sudo -u postgres /usr/lib/postgresql/14/bin/postgres -D /var/lib/postgresql/14/main -c config_file=/etc/postgresql/14/main/postgresql.conf)& 60 | sleep 1 61 | cd /tmp/zabbix-branch/database/postgresql 62 | sudo -u postgres createuser zabbix 63 | sudo -u postgres createdb -O zabbix -E Unicode -T template0 zabbix 64 | cat schema.sql | sudo -u zabbix psql zabbix 65 | cat images.sql | sudo -u zabbix psql zabbix 66 | cat data.sql | sudo -u zabbix psql zabbix 67 | sudo apache2ctl start 68 | - name: Install python3 69 | run: | 70 | sudo apt-get install -y python3 python3-pip python-is-python3 71 | pip install -r ./requirements.txt 72 | - name: Wait for Zabbix API 73 | run: | 74 | python ./.github/scripts/wait_instance_zabbix.py 75 | - name: Print Zabbix version 76 | continue-on-error: true 77 | run: | 78 | grep -Po "(?<=Changes for ).*$" /tmp/zabbix-branch/ChangeLog 2>/dev/null | head -n1 79 | - name: Integration synchronous test 80 | continue-on-error: true 81 | run: | 82 | python ./.github/scripts/$SYNC_FILE 2>/tmp/integration_sync.log >/dev/null 83 | - name: Integration asynchronous test 84 | continue-on-error: true 85 | run: | 86 | python ./.github/scripts/$ASYNC_FILE 2>/tmp/integration_async.log >/dev/null 87 | - name: Send report 88 | env: 89 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 90 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 91 | SUBJECT: Zabbix API integration test FAIL 92 | run: | 93 | err=0 94 | tail -n1 /tmp/integration_sync.log | grep "OK" 1>/dev/null || tail /tmp/integration_sync.log | python ./.github/scripts/telegram_msg.py 2>/dev/null | err=1 95 | tail -n1 /tmp/integration_async.log | grep "OK" 1>/dev/null || tail /tmp/integration_async.log | python ./.github/scripts/telegram_msg.py 2>/dev/null | err=1 96 | if [ "$err" = 1 ]; then exit 1; fi 97 | 98 | -------------------------------------------------------------------------------- /.github/workflows/integration_getter.yaml: -------------------------------------------------------------------------------- 1 | name: get 2 | run-name: Zabbix get integration test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - '**getter.py' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - '**getter.py' 13 | workflow_dispatch: 14 | 15 | env: 16 | ZABBIX_VERSION: '6.0' 17 | ZABBIX_BRANCH: master 18 | CONFIG_PATH: .github/configs/ 19 | SYNC_FILE: integration_getter_test.py 20 | ASYNC_FILE: integration_aiogetter_test.py 21 | 22 | jobs: 23 | integration: 24 | name: Integration test 25 | runs-on: ubuntu-22.04 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Install packages 30 | run: | 31 | sudo wget https://repo.zabbix.com/zabbix/${{ env.ZABBIX_VERSION }}/ubuntu/pool/main/z/zabbix-release/zabbix-release_${{ env.ZABBIX_VERSION }}-4+ubuntu22.04_all.deb 32 | sudo dpkg -i zabbix-release_${{ env.ZABBIX_VERSION }}-4+ubuntu22.04_all.deb 33 | sudo apt update && sudo apt install -y zabbix-agent 34 | - name: Prepare environment 35 | run: | 36 | sudo mkdir -p /var/log/zabbix/ 37 | sudo chown -R zabbix. /var/log/zabbix/ 38 | - name: Start Zabbix agent 39 | run: | 40 | sudo zabbix_agentd -c /etc/zabbix/zabbix_agentd.conf 41 | - name: Install python3 42 | run: | 43 | sudo apt-get install -y python3 python3-pip python-is-python3 44 | pip install -r ./requirements.txt 45 | - name: Integration synchronous test 46 | continue-on-error: true 47 | run: | 48 | python ./.github/scripts/$SYNC_FILE 2>/tmp/integration_sync.log >/dev/null 49 | - name: Integration asynchronous test 50 | continue-on-error: true 51 | run: | 52 | python ./.github/scripts/$ASYNC_FILE 2>/tmp/integration_async.log >/dev/null 53 | - name: Send report 54 | env: 55 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 56 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 57 | SUBJECT: Zabbix get integration test FAIL 58 | run: | 59 | err=0 60 | tail -n1 /tmp/integration_sync.log | grep "OK" 1>/dev/null || tail /tmp/integration_sync.log | python ./.github/scripts/telegram_msg.py 2>/dev/null | err=1 61 | tail -n1 /tmp/integration_async.log | grep "OK" 1>/dev/null || tail /tmp/integration_async.log | python ./.github/scripts/telegram_msg.py 2>/dev/null | err=1 62 | if [ "$err" = 1 ]; then exit 1; fi 63 | -------------------------------------------------------------------------------- /.github/workflows/integration_sender.yaml: -------------------------------------------------------------------------------- 1 | name: sender 2 | run-name: Zabbix sender integration test 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - '**sender.py' 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - '**sender.py' 13 | workflow_dispatch: 14 | 15 | env: 16 | ZABBIX_VERSION: '6.0' 17 | ZABBIX_BRANCH: master 18 | CONFIG_PATH: .github/configs/ 19 | SYNC_FILE: integration_sender_test.py 20 | ASYNC_FILE: integration_aiosender_test.py 21 | 22 | jobs: 23 | integration: 24 | name: Integration test 25 | runs-on: ubuntu-22.04 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Install packages 30 | run: | 31 | sudo wget https://repo.zabbix.com/zabbix/${{ env.ZABBIX_VERSION }}/ubuntu/pool/main/z/zabbix-release/zabbix-release_${{ env.ZABBIX_VERSION }}-4+ubuntu22.04_all.deb 32 | sudo dpkg -i zabbix-release_${{ env.ZABBIX_VERSION }}-4+ubuntu22.04_all.deb 33 | sudo apt update && sudo apt install -y zabbix-proxy-sqlite3 34 | - name: Prepare environment 35 | run: | 36 | sudo mkdir -p /var/log/zabbix/ 37 | sudo chown -R zabbix. /var/log/zabbix/ 38 | sudo sed -i 's#DBName=zabbix_proxy#DBName=/tmp/proxy.db#' /etc/zabbix/zabbix_proxy.conf 39 | - name: Start Zabbix proxy 40 | run: | 41 | sudo zabbix_proxy -c /etc/zabbix/zabbix_proxy.conf 42 | - name: Install python3 43 | run: | 44 | sudo apt-get install -y python3 python3-pip python-is-python3 45 | pip install -r ./requirements.txt 46 | - name: Integration synchronous test 47 | continue-on-error: true 48 | run: | 49 | python ./.github/scripts/$SYNC_FILE 2>/tmp/integration_sync.log >/dev/null 50 | - name: Integration asynchronous test 51 | continue-on-error: true 52 | run: | 53 | python ./.github/scripts/$ASYNC_FILE 2>/tmp/integration_async.log >/dev/null 54 | - name: Send report 55 | env: 56 | TBOT_TOKEN: ${{ secrets.TBOT_TOKEN }} 57 | TBOT_CHAT: ${{ vars.TBOT_CHAT }} 58 | SUBJECT: Zabbix sender integration test FAIL 59 | run: | 60 | err=0 61 | tail -n1 /tmp/integration_sync.log | grep "OK" 1>/dev/null || tail /tmp/integration_sync.log | python ./.github/scripts/telegram_msg.py 2>/dev/null | err=1 62 | tail -n1 /tmp/integration_async.log | grep "OK" 1>/dev/null || tail /tmp/integration_async.log | python ./.github/scripts/telegram_msg.py 2>/dev/null | err=1 63 | if [ "$err" = 1 ]; then exit 1; fi 64 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | run-name: Release new version 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | paths: 8 | - '**version.py' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | release: 13 | name: Release new version 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.10' 21 | - name: Get pip cache 22 | id: pip-cache 23 | run: | 24 | python -c "from pip._internal.locations import USER_CACHE_DIR; print('dir=' + USER_CACHE_DIR)" >> $GITHUB_OUTPUT 25 | - uses: actions/cache@v3 26 | with: 27 | path: ${{ steps.pip-cache.outputs.dir }} 28 | key: ${{ runner.os }}-pip 29 | - name: Install python requirements 30 | run: | 31 | python -m pip install --upgrade pip 32 | pip install setuptools wheel markdown 33 | - name: Set version env 34 | run: | 35 | echo "LIBRARY_VERSION=$(python -c "import sys; sys.path.append('.'); from zabbix_utils.version import __version__; print(__version__)")" >> $GITHUB_ENV 36 | - name: Build packages 37 | run: | 38 | python setup.py sdist bdist_wheel 39 | - name: Add version tag 40 | uses: pkgdeps/git-tag-action@v2 41 | with: 42 | github_token: ${{ secrets.GITHUB_TOKEN }} 43 | github_repo: ${{ github.repository }} 44 | git_commit_sha: ${{ github.sha }} 45 | version: ${{ env.LIBRARY_VERSION }} 46 | git_tag_prefix: "v" 47 | - name: Archive built packages 48 | uses: actions/upload-artifact@v4 49 | with: 50 | name: Archive-v${{ env.LIBRARY_VERSION }} 51 | path: dist/* 52 | - name: Create release notes 53 | run: | 54 | grep -Pzo '(?s)^## \[${{ env.LIBRARY_VERSION }}\].*?(?=## \[)' CHANGELOG.md | sed '$ s/.$//' > RELEASE_NOTES.md 55 | sed -i 's/\[${{ env.LIBRARY_VERSION }}\]/Release [v${{ env.LIBRARY_VERSION }}]/' RELEASE_NOTES.md 56 | - name: Create release 57 | uses: ncipollo/release-action@v1 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | name: v${{ env.LIBRARY_VERSION }} 62 | draft: true 63 | prerelease: false 64 | makeLatest: true 65 | skipIfReleaseExists: true 66 | replacesArtifacts: true 67 | removeArtifacts: true 68 | tag: "v${{ env.LIBRARY_VERSION }}" 69 | bodyFile: RELEASE_NOTES.md 70 | artifacts: dist/* 71 | - name: Send notification 72 | run: | 73 | python ./.github/scripts/release_notification.py 74 | working-directory: ./ 75 | env: 76 | MAIL_SERVER: ${{ secrets.MAIL_SERVER }} 77 | MAIL_PORT: ${{ secrets.MAIL_PORT }} 78 | MAIL_USER: ${{ secrets.MAIL_USER }} 79 | MAIL_PASS: ${{ secrets.MAIL_PASS }} 80 | RELEASE_RECIPIENT_LIST: ${{ secrets.RELEASE_RECIPIENT_LIST }} 81 | LIBRARY_VERSION: ${{ env.LIBRARY_VERSION }} 82 | REPOSITORY: ${{ github.repository }} 83 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: tests 2 | run-name: Library unit-tests on different platforms and versions 3 | 4 | on: 5 | push: 6 | branches: [main] 7 | pull_request: 8 | branches: [main] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | unit-tests: 13 | strategy: 14 | matrix: 15 | python-version: 16 | - "3.8" 17 | - "3.9" 18 | - "3.10" 19 | - "3.11" 20 | - "3.12" 21 | platform: 22 | - ubuntu-latest 23 | - macos-latest 24 | - windows-latest 25 | runs-on: ${{ matrix.platform }} 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v5 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install -r ./requirements.txt 36 | pip install -r ./requirements-dev.txt 37 | pip install coverage 38 | - name: Lint with flake8 39 | run: | 40 | flake8 ./zabbix_utils/ --count --select=E9,F63,F7,F82 --show-source --statistics 41 | flake8 ./zabbix_utils/ --count --exit-zero --max-complexity=20 --max-line-length=127 --statistics 42 | - name: Test with unittest 43 | run: | 44 | python -m unittest discover -s ./tests -p 'test_*.py' 45 | 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editors 2 | .vscode 3 | .idea 4 | 5 | # Compiled 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # Packaging 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | *.egg-info/ 15 | 16 | # Environments 17 | .env 18 | .venv 19 | 20 | # Coverage 21 | htmlcov/ 22 | .coverage 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [2.0.2](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.1...v2.0.2) (2024-12-12) 2 | 3 | ### Features: 4 | 5 | - added support for Zabbix 7.2 6 | - added support of proxy groups for Sender and AsyncSender 7 | 8 | ### Changes: 9 | 10 | - discontinued support for HTTP authentication for Zabbix 7.2 and newer 11 | - discontinued support for Zabbix 6.4 12 | - added examples of deleting items 13 | - added examples of how to clear item history 14 | - added examples of how to pass get request parameters 15 | 16 | ### Bug fixes: 17 | 18 | - fixed issue [#21](https://github.com/zabbix/python-zabbix-utils/issues/21) with non-obvious format of ID array passing 19 | - fixed issue [#26](https://github.com/zabbix/python-zabbix-utils/issues/26) with Sender and AsyncSender working with proxy groups 20 | - fixed small bugs and flaws 21 | 22 | ## [2.0.1](https://github.com/zabbix/python-zabbix-utils/compare/v2.0.0...v2.0.1) (2024-09-18) 23 | 24 | ### Features: 25 | 26 | - added ssl_context argument to ZabbixAPI to allow more flexible configuration of SSL connections 27 | - added support of SSL connection configuration to AsyncZabbixAPI 28 | 29 | ## [2.0.0](https://github.com/zabbix/python-zabbix-utils/compare/v1.1.1...v2.0.0) (2024-04-12) 30 | 31 | ### Features: 32 | 33 | - added asynchronous modules: AsyncZabbixAPI, AsyncSender, AsyncGetter 34 | - added examples of working with asynchronous modules 35 | 36 | ### Bug fixes: 37 | 38 | - fixed issue [#7](https://github.com/zabbix/python-zabbix-utils/issues/7) in examples of PSK using on Linux 39 | - fixed small bugs and flaws 40 | 41 | ## [1.1.1](https://github.com/zabbix/python-zabbix-utils/compare/v1.1.0...v1.1.1) (2024-03-06) 42 | 43 | ### Changes: 44 | 45 | - removed external requirements 46 | 47 | ## [1.1.0](https://github.com/zabbix/python-zabbix-utils/compare/v1.0.3...v1.1.0) (2024-01-23) 48 | 49 | ### Breaking Changes: 50 | 51 | - changed the format of the Sender response 52 | - changed the format of the Getter response 53 | 54 | ### Features: 55 | 56 | - implemented support for specifying Zabbix clusters in Sender 57 | - implemented pre-processing of the agent response 58 | 59 | ### Bug fixes: 60 | 61 | - fixed issue with hiding private (sensitive) fields in the log 62 | - fixed small bugs and flaws 63 | 64 | ## [1.0.3](https://github.com/zabbix/python-zabbix-utils/compare/v1.0.2...v1.0.3) (2024-01-09) 65 | 66 | ### Documentation 67 | 68 | - added support for Python 3.12 69 | - discontinued support for Python 3.7 70 | 71 | ### Bug fixes: 72 | 73 | - fixed issue with hiding private (sensitive) information in the log. 74 | - fixed small bugs and flaws. 75 | 76 | ## [1.0.2](https://github.com/zabbix/python-zabbix-utils/compare/v1.0.1...v1.0.2) (2023-12-15) 77 | 78 | ### Bug fixes: 79 | 80 | - added trailing underscores as workaround to use Python keywords as names of API object or method 81 | - changed TypeError to ValueError for the exception during version parsing. 82 | - fixed compression support for Sender and Getter. 83 | - made refactoring of some parts of the code. 84 | - fixed small bugs and flaws. 85 | 86 | ## [1.0.1](https://github.com/zabbix/python-zabbix-utils/compare/v1.0.0...v1.0.1) (2023-11-27) 87 | 88 | ### Bug fixes: 89 | 90 | - removed deprecated API fields from examples and README. 91 | - removed "Get started" section from README for PyPI. 92 | - fixed small flaws. 93 | 94 | ## [1.0.0](https://github.com/zabbix/python-zabbix-utils/tree/v1.0.0) (2023-11-17) 95 | 96 | Initial release 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (C) 2001-2023 Zabbix SIA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to use, 8 | copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the 9 | Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 18 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 20 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /examples/api/asynchronous/auth_by_token.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | 10 | # Zabbix server URL or IP address. 11 | ZABBIX_SERVER = "127.0.0.1" 12 | 13 | # Use an authentication token generated via the web interface or 14 | # API instead of standard authentication by username and password. 15 | ZABBIX_TOKEN = "8jF7sGh2Rp4TlQ1ZmXo0uYv3Bc6AiD9E" 16 | 17 | 18 | async def main(): 19 | """ 20 | The main function to perform asynchronous tasks. 21 | """ 22 | 23 | # Create an instance of the AsyncZabbixAPI class. 24 | api = AsyncZabbixAPI(ZABBIX_SERVER) 25 | 26 | # Authenticating with Zabbix API using the provided token. 27 | await api.login(token=ZABBIX_TOKEN) 28 | 29 | # Retrieve a list of users, including their user ID and name 30 | users = await api.user.get( 31 | output=['userid', 'name'] 32 | ) 33 | 34 | # Print the names of the retrieved users 35 | for user in users: 36 | print(user['name']) 37 | 38 | # Close asynchronous connection 39 | await api.logout() 40 | 41 | # Run the main coroutine 42 | asyncio.run(main()) 43 | -------------------------------------------------------------------------------- /examples/api/asynchronous/check_auth_state.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | 10 | # Zabbix server URL or IP address 11 | ZABBIX_SERVER = "127.0.0.1" 12 | 13 | # Zabbix server authentication credentials 14 | ZABBIX_AUTH = { 15 | "user": "Admin", # Zabbix user name for authentication 16 | "password": "zabbix" # Zabbix user password for authentication 17 | } 18 | 19 | 20 | async def main(): 21 | """ 22 | The main function to perform asynchronous tasks. 23 | """ 24 | 25 | # Create an instance of the AsyncZabbixAPI class 26 | api = AsyncZabbixAPI(ZABBIX_SERVER) 27 | 28 | # Authenticating with Zabbix API using the provided username and password. 29 | await api.login(**ZABBIX_AUTH) 30 | 31 | # Some actions when your session can be released 32 | # For example, api.logout() 33 | 34 | # Check if authentication is still valid 35 | if await api.check_auth(): 36 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 37 | hosts = await api.host.get( 38 | output=['hostid', 'name'] 39 | ) 40 | 41 | # Print the names of the retrieved hosts 42 | for host in hosts: 43 | print(host['name']) 44 | 45 | # Logout to release the Zabbix API session and close asynchronous connection 46 | await api.logout() 47 | 48 | # Run the main coroutine 49 | asyncio.run(main()) 50 | -------------------------------------------------------------------------------- /examples/api/asynchronous/clear_history.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI, APIRequestError 8 | 9 | # Zabbix server URL or IP address 10 | ZABBIX_SERVER = "127.0.0.1" 11 | 12 | # Zabbix server authentication credentials 13 | ZABBIX_AUTH = { 14 | "user": "Admin", # Zabbix user name for authentication 15 | "password": "zabbix" # Zabbix user password for authentication 16 | } 17 | 18 | # IDs of items for which the history should be cleared 19 | ITEM_IDS = [70060] 20 | 21 | 22 | async def main(): 23 | """ 24 | The main function to perform asynchronous tasks. 25 | """ 26 | 27 | # Create an instance of the AsyncZabbixAPI class 28 | api = AsyncZabbixAPI(ZABBIX_SERVER) 29 | 30 | # Authenticating with Zabbix API using the provided username and password. 31 | await api.login(**ZABBIX_AUTH) 32 | 33 | # Clear history for items with specified IDs 34 | try: 35 | await api.history.clear(*ITEM_IDS) 36 | 37 | # Alternative way to do the same (since v2.0.2): 38 | # await api.history.clear(ITEM_IDS) 39 | except APIRequestError as e: 40 | print(f"An error occurred when attempting to delete items: {e}") 41 | else: 42 | # Logout to release the Zabbix API session 43 | await api.logout() 44 | 45 | # Run the main coroutine 46 | asyncio.run(main()) 47 | -------------------------------------------------------------------------------- /examples/api/asynchronous/custom_client_session.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | from aiohttp import ClientSession, TCPConnector 9 | 10 | 11 | # Zabbix server URL or IP address 12 | ZABBIX_SERVER = "127.0.0.1" 13 | 14 | # Zabbix server authentication credentials 15 | ZABBIX_AUTH = { 16 | "user": "Admin", # Zabbix user name for authentication 17 | "password": "zabbix" # Zabbix user password for authentication 18 | } 19 | 20 | 21 | async def main(): 22 | """ 23 | The main function to perform asynchronous tasks. 24 | """ 25 | 26 | # Create an asynchronous client session for HTTP requests 27 | client_session = ClientSession( 28 | connector=TCPConnector(ssl=False) 29 | ) 30 | 31 | # Create an instance of the AsyncZabbixAPI class 32 | api = AsyncZabbixAPI( 33 | url=ZABBIX_SERVER, 34 | client_session=client_session 35 | ) 36 | 37 | # Authenticating with Zabbix API using the provided username and password. 38 | await api.login(**ZABBIX_AUTH) 39 | 40 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 41 | hosts = await api.host.get( 42 | output=['hostid', 'name'] 43 | ) 44 | 45 | # Print the names of the retrieved hosts 46 | for host in hosts: 47 | print(host['name']) 48 | 49 | # Logout to release the Zabbix API session 50 | await api.logout() 51 | 52 | # Close asynchronous client session 53 | await client_session.close() 54 | 55 | # Run the main coroutine 56 | asyncio.run(main()) 57 | -------------------------------------------------------------------------------- /examples/api/asynchronous/custom_ssl_context.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | import asyncio 8 | from zabbix_utils import AsyncZabbixAPI 9 | from aiohttp import ClientSession, TCPConnector 10 | 11 | 12 | # Zabbix server URL or IP address 13 | ZABBIX_SERVER = "https://example.com" 14 | 15 | # Zabbix server authentication credentials 16 | ZABBIX_AUTH = { 17 | "user": "Admin", # Zabbix user name for authentication 18 | "password": "zabbix" # Zabbix user password for authentication 19 | } 20 | 21 | 22 | async def main(): 23 | """ 24 | The main function to perform asynchronous tasks. 25 | """ 26 | 27 | # Create a default SSL context for secure connections 28 | # Load a custom certificate from the specified file path to verify the server 29 | ctx = ssl.create_default_context() 30 | ctx.load_verify_locations("/path/to/certificate.crt") 31 | 32 | # Create an asynchronous client session for HTTP requests 33 | client_session = ClientSession( 34 | connector=TCPConnector(ssl=ctx) 35 | ) 36 | 37 | # Create an instance of the AsyncZabbixAPI class 38 | api = AsyncZabbixAPI( 39 | url=ZABBIX_SERVER, 40 | client_session=client_session 41 | ) 42 | 43 | # Authenticating with Zabbix API using the provided username and password. 44 | await api.login(**ZABBIX_AUTH) 45 | 46 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 47 | hosts = await api.host.get( 48 | output=['hostid', 'name'] 49 | ) 50 | 51 | # Print the names of the retrieved hosts 52 | for host in hosts: 53 | print(host['name']) 54 | 55 | # Logout to release the Zabbix API session 56 | await api.logout() 57 | 58 | # Close asynchronous client session 59 | await client_session.close() 60 | 61 | # Run the main coroutine 62 | asyncio.run(main()) 63 | -------------------------------------------------------------------------------- /examples/api/asynchronous/delete_items.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI, APIRequestError 8 | 9 | # Zabbix server URL or IP address 10 | ZABBIX_SERVER = "127.0.0.1" 11 | 12 | # Zabbix server authentication credentials 13 | ZABBIX_AUTH = { 14 | "user": "Admin", # Zabbix user name for authentication 15 | "password": "zabbix" # Zabbix user password for authentication 16 | } 17 | 18 | # Item IDs to be deleted 19 | ITEM_IDS = [70060] 20 | 21 | 22 | async def main(): 23 | """ 24 | The main function to perform asynchronous tasks. 25 | """ 26 | 27 | # Create an instance of the AsyncZabbixAPI class 28 | api = AsyncZabbixAPI(ZABBIX_SERVER) 29 | 30 | # Authenticating with Zabbix API using the provided username and password. 31 | await api.login(**ZABBIX_AUTH) 32 | 33 | # Delete items with specified IDs 34 | try: 35 | await api.item.delete(*ITEM_IDS) 36 | 37 | # Alternative way to do the same (since v2.0.2): 38 | # await api.item.delete(ITEM_IDS) 39 | except APIRequestError as e: 40 | print(f"An error occurred when attempting to delete items: {e}") 41 | else: 42 | # Logout to release the Zabbix API session 43 | await api.logout() 44 | 45 | # Run the main coroutine 46 | asyncio.run(main()) 47 | -------------------------------------------------------------------------------- /examples/api/asynchronous/disabling_validate_certs.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | 10 | # SSL certificate verification will be ignored. 11 | # This can be useful in some cases, but it also poses security risks because 12 | # it makes the connection susceptible to man-in-the-middle attacks. 13 | ZABBIX_PARAMS = { 14 | "url": "127.0.0.1", 15 | "validate_certs": False 16 | } 17 | 18 | # Zabbix server authentication credentials. 19 | ZABBIX_AUTH = { 20 | "user": "Admin", 21 | "password": "zabbix" 22 | } 23 | 24 | 25 | async def main(): 26 | """ 27 | The main function to perform asynchronous tasks. 28 | """ 29 | 30 | # Create an instance of the AsyncZabbixAPI class with the specified authentication details. 31 | # Note: Ignoring SSL certificate validation may expose the connection to security risks. 32 | api = AsyncZabbixAPI(**ZABBIX_PARAMS) 33 | 34 | # Authenticating with Zabbix API using the provided username and password. 35 | await api.login(**ZABBIX_AUTH) 36 | 37 | # Retrieve a list of users from the Zabbix server, including their user ID and name. 38 | users = await api.user.get( 39 | output=['userid', 'name'] 40 | ) 41 | 42 | # Print the names of the retrieved users. 43 | for user in users: 44 | print(user['name']) 45 | 46 | # Logout to release the Zabbix API session. 47 | await api.logout() 48 | 49 | # Run the main coroutine 50 | asyncio.run(main()) 51 | -------------------------------------------------------------------------------- /examples/api/asynchronous/export_templates.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | 10 | # Zabbix server URL or IP address 11 | ZABBIX_SERVER = "127.0.0.1" 12 | 13 | # Zabbix server authentication credentials 14 | ZABBIX_AUTH = { 15 | "user": "Admin", # Zabbix user name for authentication 16 | "password": "zabbix" # Zabbix user password for authentication 17 | } 18 | 19 | # Template IDs to be exported 20 | TEMPLATE_IDS = [10050] 21 | 22 | # File path and format for exporting configuration 23 | FILE_PATH = "templates_export_example.{}" 24 | 25 | 26 | async def main(): 27 | """ 28 | The main function to perform asynchronous tasks. 29 | """ 30 | 31 | # Create an instance of the ZabbixAPI class with the specified authentication details 32 | api = AsyncZabbixAPI(ZABBIX_SERVER) 33 | 34 | # Authenticating with Zabbix API using the provided token. 35 | await api.login(**ZABBIX_AUTH) 36 | 37 | # Determine the export file format based on the Zabbix API version 38 | export_format = "yaml" 39 | if api.version < 5.4: 40 | export_format = "xml" 41 | 42 | # Export configuration for specified template IDs 43 | configuration = await api.configuration.export( 44 | options={ 45 | "templates": TEMPLATE_IDS 46 | }, 47 | format=export_format 48 | ) 49 | 50 | # Write the exported configuration to a file 51 | with open(FILE_PATH.format(export_format), mode='w', encoding='utf-8') as f: 52 | f.write(configuration) 53 | 54 | # Logout to release the Zabbix API session 55 | await api.logout() 56 | 57 | # Run the main coroutine 58 | asyncio.run(main()) 59 | -------------------------------------------------------------------------------- /examples/api/asynchronous/get_request_parameters.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | # Zabbix server URL or IP address 10 | ZABBIX_SERVER = "127.0.0.1" 11 | 12 | # Zabbix server authentication credentials 13 | ZABBIX_AUTH = { 14 | "user": "Admin", # Zabbix user name for authentication 15 | "password": "zabbix" # Zabbix user password for authentication 16 | } 17 | 18 | 19 | async def main(): 20 | """ 21 | The main function to perform asynchronous tasks. 22 | """ 23 | 24 | # Create an instance of the AsyncZabbixAPI class 25 | api = AsyncZabbixAPI(ZABBIX_SERVER) 26 | 27 | # Authenticating with Zabbix API using the provided username and password. 28 | await api.login(**ZABBIX_AUTH) 29 | 30 | # There are only three ways to pass parameters of type dictionary: 31 | # 32 | # 1. Specifying values directly with their keys: 33 | problems = await api.problem.get(tags=[{"tag": "scope", "value": "notice", "operator": "0"}]) 34 | # 35 | # 2. Unpacking dictionary keys and values using `**`: 36 | # request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]} 37 | # problems = await api.problem.get(**request_params) 38 | # 39 | # 3. Passing the dictionary directly as an argument (since v2.0.2): 40 | # request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]} 41 | # problems = await api.problem.get(request_params) 42 | 43 | # Print the names of the retrieved users 44 | for problem in problems: 45 | print(problem['name']) 46 | 47 | # Logout to release the Zabbix API session 48 | await api.logout() 49 | 50 | # Run the main coroutine 51 | asyncio.run(main()) 52 | -------------------------------------------------------------------------------- /examples/api/asynchronous/use_context_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | 10 | # Zabbix server details and authentication credentials 11 | ZABBIX_SERVER = "127.0.0.1" # Zabbix server URL or IP address 12 | ZABBIX_USER = "Admin" # Zabbix user name for authentication 13 | ZABBIX_PASSWORD = "zabbix" # Zabbix user password for authentication 14 | 15 | 16 | async def main(): 17 | """ 18 | The main function to perform asynchronous tasks. 19 | """ 20 | 21 | # Use a context manager for automatic logout and 22 | # close asynchronous session upon completion of the request. 23 | # Each time it's created it performs synchronously "apiinfo.version". 24 | # Highly recommended not to use it many times in a single script. 25 | async with AsyncZabbixAPI(url=ZABBIX_SERVER) as api: 26 | # Authenticate with the Zabbix API using the provided user credentials 27 | await api.login(user=ZABBIX_USER, password=ZABBIX_PASSWORD) 28 | 29 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 30 | hosts = await api.host.get( 31 | output=['hostid', 'name'] 32 | ) 33 | 34 | # Print the names of the retrieved hosts 35 | for host in hosts: 36 | print(host['name']) 37 | 38 | # Automatic logout occurs when the code block exits due to the context manager. 39 | 40 | # Run the main coroutine 41 | asyncio.run(main()) 42 | -------------------------------------------------------------------------------- /examples/api/asynchronous/using_http_auth.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncZabbixAPI 8 | 9 | 10 | async def main(): 11 | """ 12 | The main function to perform asynchronous tasks. 13 | """ 14 | 15 | # Create an instance of the AsyncZabbixAPI class with the Zabbix server URL 16 | # Set Basic Authentication credentials for Zabbix API requests 17 | # Basic Authentication - a simple authentication mechanism used in HTTP. 18 | # It involves sending a username and password with each HTTP request. 19 | api = AsyncZabbixAPI( 20 | url="http://127.0.0.1", 21 | http_user="user", 22 | http_password="p@$sw0rd" 23 | ) 24 | 25 | # Login to the Zabbix API using provided user credentials 26 | await api.login(user="Admin", password="zabbix") 27 | 28 | # Retrieve a list of users from the Zabbix server, including their user ID and name 29 | users = await api.user.get( 30 | output=['userid', 'name'] 31 | ) 32 | 33 | # Print the names of the retrieved users 34 | for user in users: 35 | print(user['name']) 36 | 37 | # Logout to release the Zabbix API session 38 | await api.logout() 39 | 40 | # Run the main coroutine 41 | asyncio.run(main()) 42 | -------------------------------------------------------------------------------- /examples/api/synchronous/auth_by_token.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Use an authentication token generated via the web interface or 9 | # API instead of standard authentication by username and password. 10 | ZABBIX_AUTH = { 11 | "url": "127.0.0.1", 12 | "token": "8jF7sGh2Rp4TlQ1ZmXo0uYv3Bc6AiD9E" 13 | } 14 | 15 | # Create an instance of the ZabbixAPI class with the specified authentication details 16 | api = ZabbixAPI(**ZABBIX_AUTH) 17 | 18 | # Retrieve a list of users, including their user ID and name 19 | users = api.user.get( 20 | output=['userid', 'name'] 21 | ) 22 | 23 | # Print the names of the retrieved users 24 | for user in users: 25 | print(user['name']) 26 | -------------------------------------------------------------------------------- /examples/api/synchronous/check_auth_state.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_AUTH = { 10 | "url": "127.0.0.1", # Zabbix server URL or IP address 11 | "user": "Admin", # Zabbix user name for authentication 12 | "password": "zabbix" # Zabbix user password for authentication 13 | } 14 | 15 | # Create an instance of the ZabbixAPI class with the specified authentication details 16 | api = ZabbixAPI(**ZABBIX_AUTH) 17 | 18 | # Some actions when your session can be released 19 | # For example, api.logout() 20 | 21 | # Check if authentication is still valid 22 | if api.check_auth(): 23 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 24 | hosts = api.host.get( 25 | output=['hostid', 'name'] 26 | ) 27 | 28 | # Print the names of the retrieved hosts 29 | for host in hosts: 30 | print(host['name']) 31 | 32 | # Logout to release the Zabbix API session 33 | api.logout() 34 | -------------------------------------------------------------------------------- /examples/api/synchronous/clear_history.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI, APIRequestError 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_AUTH = { 10 | "url": "127.0.0.1", # Zabbix server URL or IP address 11 | "user": "Admin", # Zabbix user name for authentication 12 | "password": "zabbix" # Zabbix user password for authentication 13 | } 14 | 15 | # IDs of items for which the history should be cleared 16 | ITEM_IDS = [70060] 17 | 18 | # Create an instance of the ZabbixAPI class with the specified authentication details 19 | api = ZabbixAPI(**ZABBIX_AUTH) 20 | 21 | # Clear history for items with specified IDs 22 | try: 23 | api.history.clear(*ITEM_IDS) 24 | 25 | # Alternative way to do the same (since v2.0.2): 26 | # api.history.clear(*ITEM_IDS) 27 | except APIRequestError as e: 28 | print(f"An error occurred when attempting to clear items' history: {e}") 29 | 30 | # Logout to release the Zabbix API session 31 | api.logout() 32 | -------------------------------------------------------------------------------- /examples/api/synchronous/custom_ssl_context.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | from zabbix_utils import ZabbixAPI 8 | 9 | # Create a default SSL context for secure connections 10 | # Load a custom certificate from the specified file path to verify the server 11 | ctx = ssl.create_default_context() 12 | ctx.load_verify_locations("/path/to/certificate.crt") 13 | 14 | # Create an instance of the ZabbixAPI class with the Zabbix server URL 15 | # Set ssl_context value for Zabbix API requests. 16 | ZABBIX_AUTH = { 17 | "url": "https://example.com", 18 | "user": "Admin", 19 | "password": "zabbix", 20 | "ssl_context": ctx 21 | } 22 | 23 | # Login to the Zabbix API using provided user credentials 24 | api = ZabbixAPI(**ZABBIX_AUTH) 25 | 26 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 27 | hosts = api.host.get( 28 | output=['hostid', 'name'] 29 | ) 30 | 31 | # Print the names of the retrieved hosts 32 | for host in hosts: 33 | print(host['name']) 34 | 35 | # Logout to release the Zabbix API session 36 | api.logout() 37 | -------------------------------------------------------------------------------- /examples/api/synchronous/delete_items.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI, APIRequestError 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_AUTH = { 10 | "url": "127.0.0.1", # Zabbix server URL or IP address 11 | "user": "Admin", # Zabbix user name for authentication 12 | "password": "zabbix" # Zabbix user password for authentication 13 | } 14 | 15 | # Item IDs to be deleted 16 | ITEM_IDS = [70060] 17 | 18 | # Create an instance of the ZabbixAPI class with the specified authentication details 19 | api = ZabbixAPI(**ZABBIX_AUTH) 20 | 21 | # Delete items with specified IDs 22 | try: 23 | api.item.delete(*ITEM_IDS) 24 | 25 | # Alternative way to do the same (since v2.0.2): 26 | # api.item.delete(ITEM_IDS) 27 | except APIRequestError as e: 28 | print(f"An error occurred when attempting to delete items: {e}") 29 | 30 | # Logout to release the Zabbix API session 31 | api.logout() 32 | -------------------------------------------------------------------------------- /examples/api/synchronous/disabling_validate_certs.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # SSL certificate verification will be ignored. 9 | # This can be useful in some cases, but it also poses security risks because 10 | # it makes the connection susceptible to man-in-the-middle attacks. 11 | ZABBIX_AUTH = { 12 | "url": "127.0.0.1", 13 | "user": "Admin", 14 | "password": "zabbix", 15 | "validate_certs": False 16 | } 17 | 18 | # Create an instance of the ZabbixAPI class with the specified authentication details 19 | # Note: Ignoring SSL certificate validation may expose the connection to security risks. 20 | api = ZabbixAPI(**ZABBIX_AUTH) 21 | 22 | # Retrieve a list of users from the Zabbix server, including their user ID and name 23 | users = api.user.get( 24 | output=['userid', 'name'] 25 | ) 26 | 27 | # Print the names of the retrieved users 28 | for user in users: 29 | print(user['name']) 30 | 31 | # Logout to release the Zabbix API session 32 | api.logout() 33 | -------------------------------------------------------------------------------- /examples/api/synchronous/export_templates.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_AUTH = { 10 | "url": "127.0.0.1", # Zabbix server URL or IP address 11 | "user": "Admin", # Zabbix user name for authentication 12 | "password": "zabbix" # Zabbix user password for authentication 13 | } 14 | 15 | # Template IDs to be exported 16 | TEMPLATE_IDS = [10050] 17 | 18 | # File path and format for exporting configuration 19 | FILE_PATH = "templates_export_example.{}" 20 | 21 | # Create an instance of the ZabbixAPI class with the specified authentication details 22 | api = ZabbixAPI(**ZABBIX_AUTH) 23 | 24 | # Determine the export file format based on the Zabbix API version 25 | export_format = "yaml" 26 | if api.version < 5.4: 27 | export_format = "xml" 28 | 29 | # Export configuration for specified template IDs 30 | configuration = api.configuration.export( 31 | options={ 32 | "templates": TEMPLATE_IDS 33 | }, 34 | format=export_format 35 | ) 36 | 37 | # Write the exported configuration to a file 38 | with open(FILE_PATH.format(export_format), mode='w', encoding='utf-8') as f: 39 | f.write(configuration) 40 | 41 | # Logout to release the Zabbix API session 42 | api.logout() 43 | -------------------------------------------------------------------------------- /examples/api/synchronous/get_request_parameters.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_AUTH = { 10 | "url": "127.0.0.1", # Zabbix server URL or IP address 11 | "user": "Admin", # Zabbix user name for authentication 12 | "password": "zabbix" # Zabbix user password for authentication 13 | } 14 | 15 | # Create an instance of the ZabbixAPI class with the specified authentication details 16 | api = ZabbixAPI(**ZABBIX_AUTH) 17 | 18 | # There are only three ways to pass parameters of type dictionary: 19 | # 20 | # 1. Specifying values directly with their keys: 21 | problems = api.problem.get(tags=[{"tag": "scope", "value": "notice", "operator": "0"}]) 22 | # 23 | # 2. Unpacking dictionary keys and values using `**`: 24 | # request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]} 25 | # problems = api.problem.get(**request_params) 26 | # 27 | # 3. Passing the dictionary directly as an argument (since v2.0.2): 28 | # request_params = {"tags": [{"tag": "scope", "value": "notice", "operator": "0"}]} 29 | # problems = api.problem.get(request_params) 30 | 31 | # Print the names of the retrieved users 32 | for problem in problems: 33 | print(problem['name']) 34 | 35 | # Logout to release the Zabbix API session 36 | api.logout() 37 | -------------------------------------------------------------------------------- /examples/api/synchronous/token_auth_if_supported.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_SERVER = "127.0.0.1" # Zabbix server URL or IP address 10 | ZABBIX_USER = "Admin" # Zabbix user name for authentication 11 | ZABBIX_PASSWORD = "zabbix" # Zabbix user password for authentication 12 | ZABBIX_TOKEN = "8jF7sGh2Rp4TlQ1ZmXo0uYv3Bc6AiD9E" # Authentication token for API access 13 | 14 | # Create an instance of the ZabbixAPI class with the specified Zabbix server URL 15 | api = ZabbixAPI(url=ZABBIX_SERVER) 16 | 17 | # Check Zabbix API version and authenticate accordingly 18 | # Zabbix API version can be compared with version expressed in float (major) or 19 | # string (full, i.e. "major.minor"). 20 | if api.version >= 5.4: 21 | # If Zabbix API version is 5.4 or newer, use token-based authentication 22 | api.login(token=ZABBIX_TOKEN) 23 | else: 24 | # If Zabbix API version is older than 5.4, use traditional username and password authentication 25 | api.login(user=ZABBIX_USER, password=ZABBIX_PASSWORD) 26 | 27 | # Retrieve a list of users from the Zabbix server, including their user ID and name 28 | users = api.user.get( 29 | output=['userid', 'name'] 30 | ) 31 | 32 | # Print the names of the retrieved users 33 | for user in users: 34 | print(user['name']) 35 | 36 | # Logout to release the Zabbix API session 37 | api.logout() 38 | -------------------------------------------------------------------------------- /examples/api/synchronous/use_context_manager.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Zabbix server details and authentication credentials 9 | ZABBIX_SERVER = "127.0.0.1" # Zabbix server URL or IP address 10 | ZABBIX_USER = "Admin" # Zabbix user name for authentication 11 | ZABBIX_PASSWORD = "zabbix" # Zabbix user password for authentication 12 | 13 | # Use a context manager for automatic logout upon completion of the request. 14 | # Each time it's created it performs "login" and "apiinfo.version". 15 | # Highly recommended not to use it many times in a single script. 16 | with ZabbixAPI(url=ZABBIX_SERVER) as api: 17 | # Authenticate with the Zabbix API using the provided user credentials 18 | api.login(user=ZABBIX_USER, password=ZABBIX_PASSWORD) 19 | 20 | # Retrieve a list of hosts from the Zabbix server, including their host ID and name 21 | hosts = api.host.get( 22 | output=['hostid', 'name'] 23 | ) 24 | 25 | # Print the names of the retrieved hosts 26 | for host in hosts: 27 | print(host['name']) 28 | 29 | # Automatic logout occurs when the code block exits due to the context manager. 30 | -------------------------------------------------------------------------------- /examples/api/synchronous/using_http_auth.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ZabbixAPI 7 | 8 | # Create an instance of the ZabbixAPI class with the Zabbix server URL 9 | # Set Basic Authentication credentials for Zabbix API requests 10 | # Basic Authentication - a simple authentication mechanism used in HTTP. 11 | # It involves sending a username and password with each HTTP request. 12 | api = ZabbixAPI( 13 | url="http://127.0.0.1", 14 | http_user="user", 15 | http_password="p@$sw0rd" 16 | ) 17 | 18 | # Login to the Zabbix API using provided user credentials 19 | api.login(user="Admin", password="zabbix") 20 | 21 | # Retrieve a list of users from the Zabbix server, including their user ID and name 22 | users = api.user.get( 23 | output=['userid', 'name'] 24 | ) 25 | 26 | # Print the names of the retrieved users 27 | for user in users: 28 | print(user['name']) 29 | 30 | # Logout to release the Zabbix API session 31 | api.logout() 32 | -------------------------------------------------------------------------------- /examples/get/asynchronous/custom_source_ip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncGetter 8 | 9 | 10 | async def main(): 11 | """ 12 | The main function to perform asynchronous tasks. 13 | """ 14 | 15 | # Create a AsyncGetter instance with specified parameters 16 | # Parameters: (host, port, source_ip) 17 | agent = AsyncGetter("127.0.0.1", 10050, source_ip="10.10.1.5") 18 | 19 | # Send a Zabbix agent query for system information (e.g., uname) 20 | resp = await agent.get('system.uname') 21 | 22 | # Check if there was an error in the response 23 | if resp.error: 24 | # Print the error message 25 | print("An error occurred while trying to get the value:", resp.error) 26 | else: 27 | # Print the value obtained for the specified item key item 28 | print("Received value:", resp.value) 29 | 30 | # Run the main coroutine 31 | asyncio.run(main()) 32 | -------------------------------------------------------------------------------- /examples/get/asynchronous/getting_value.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import sys 7 | import json 8 | import asyncio 9 | from zabbix_utils import AsyncGetter 10 | 11 | 12 | async def main(): 13 | """ 14 | The main function to perform asynchronous tasks. 15 | """ 16 | 17 | # Create a AsyncGetter instance for querying Zabbix agent 18 | agent = AsyncGetter(host='127.0.0.1', port=10050) 19 | 20 | # Send a Zabbix agent query for network interface discovery 21 | resp = await agent.get('net.if.discovery') 22 | 23 | # Check if there was an error in the response 24 | if resp.error: 25 | # Print the error message 26 | print("An error occurred while trying to get the value:", resp.error) 27 | # Exit the script 28 | sys.exit() 29 | 30 | try: 31 | # Attempt to parse the JSON response 32 | resp_list = json.loads(resp.value) 33 | except json.decoder.JSONDecodeError: 34 | print("Agent response decoding fails") 35 | # Exit the script if JSON decoding fails 36 | sys.exit() 37 | 38 | # Iterate through the discovered network interfaces and print their names 39 | for interface in resp_list: 40 | print(interface['{#IFNAME}']) 41 | 42 | # Run the main coroutine 43 | asyncio.run(main()) 44 | -------------------------------------------------------------------------------- /examples/get/synchronous/custom_source_ip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import Getter 7 | 8 | # Create a Getter instance with specified parameters 9 | # Parameters: (host, port, source_ip) 10 | agent = Getter("127.0.0.1", 10050, source_ip="10.10.1.5") 11 | 12 | # Send a Zabbix agent query for system information (e.g., uname) 13 | resp = agent.get('system.uname') 14 | 15 | # Check if there was an error in the response 16 | if resp.error: 17 | # Print the error message 18 | print("An error occurred while trying to get the value:", resp.error) 19 | else: 20 | # Print the value obtained for the specified item key item 21 | print("Received value:", resp.value) 22 | -------------------------------------------------------------------------------- /examples/get/synchronous/getting_value.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import sys 7 | import json 8 | from zabbix_utils import Getter 9 | 10 | # Create a Getter instance for querying Zabbix agent 11 | agent = Getter(host='127.0.0.1', port=10050) 12 | 13 | # Send a Zabbix agent query for network interface discovery 14 | resp = agent.get('net.if.discovery') 15 | 16 | # Check if there was an error in the response 17 | if resp.error: 18 | # Print the error message 19 | print("An error occurred while trying to get the value:", resp.error) 20 | # Exit the script 21 | sys.exit() 22 | 23 | try: 24 | # Attempt to parse the JSON response 25 | resp_list = json.loads(resp.value) 26 | except json.decoder.JSONDecodeError: 27 | print("Agent response decoding fails") 28 | # Exit the script if JSON decoding fails 29 | sys.exit() 30 | 31 | # Iterate through the discovered network interfaces and print their names 32 | for interface in resp_list: 33 | print(interface['{#IFNAME}']) 34 | -------------------------------------------------------------------------------- /examples/get/synchronous/psk_wrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | from zabbix_utils import Getter 8 | 9 | # Try importing sslpsk3, fall back to sslpsk2 if not available 10 | try: 11 | import sslpsk3 as sslpsk 12 | except ImportError: 13 | # Import sslpsk2 if sslpsk3 is not available 14 | import sslpsk2 as sslpsk 15 | 16 | 17 | # PSK wrapper function for SSL connection 18 | def psk_wrapper(sock): 19 | # Pre-Shared Key (PSK) and PSK Identity 20 | psk = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') 21 | psk_identity = b'PSKID' 22 | 23 | # Wrap the socket using sslpsk to establish an SSL connection with PSK 24 | return sslpsk.wrap_socket( 25 | sock, 26 | ssl_version=ssl.PROTOCOL_TLSv1_2, 27 | ciphers='ECDHE-PSK-AES128-CBC-SHA256', 28 | psk=(psk, psk_identity) 29 | ) 30 | 31 | 32 | # Zabbix agent parameters 33 | ZABBIX_AGENT = "127.0.0.1" 34 | ZABBIX_PORT = 10050 35 | 36 | # Create a Getter instance with PSK support 37 | agent = Getter( 38 | host=ZABBIX_AGENT, 39 | port=ZABBIX_PORT, 40 | socket_wrapper=psk_wrapper 41 | ) 42 | 43 | # Send a Zabbix agent query for system information (e.g., uname) 44 | resp = agent.get('system.uname') 45 | 46 | # Check if there was an error in the response 47 | if resp.error: 48 | # Print the error message 49 | print("An error occurred while trying to get the value:", resp.error) 50 | else: 51 | # Print the value obtained for the specified item key item 52 | print("Received value:", resp.value) 53 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/agent_clusters_using.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import ItemValue, AsyncSender 8 | 9 | 10 | # You can create an instance of AsyncSender specifying server address and port: 11 | # 12 | # sender = AsyncSender(server='127.0.0.1', port=10051) 13 | # 14 | # Or you can create an instance of AsyncSender specifying a list of Zabbix clusters: 15 | zabbix_clusters = [ 16 | ['zabbix.cluster1.node1', 'zabbix.cluster1.node2:10051'], 17 | ['zabbix.cluster2.node1:10051', 'zabbix.cluster2.node2:20051', 'zabbix.cluster2.node3'] 18 | ] 19 | 20 | # List of ItemValue instances representing items to be sent 21 | items = [ 22 | ItemValue('host1', 'item.key1', 10), 23 | ItemValue('host1', 'item.key2', 'test message'), 24 | ItemValue('host2', 'item.key1', -1, 1695713666), 25 | ItemValue('host3', 'item.key1', '{"msg":"test message"}'), 26 | ItemValue('host2', 'item.key1', 0, 1695713666, 100) 27 | ] 28 | 29 | 30 | async def main(): 31 | """ 32 | The main function to perform asynchronous tasks. 33 | """ 34 | 35 | sender = AsyncSender(clusters=zabbix_clusters) 36 | # You can also specify Zabbix clusters at the same time with server address and port: 37 | # 38 | # sender = AsyncSender(server='127.0.0.1', port=10051, clusters=zabbix_clusters) 39 | # 40 | # In such case, specified server address and port will be appended to the cluster list 41 | # as a cluster of a single node 42 | 43 | # Send multiple items to the Zabbix server/proxy and receive response 44 | response = await sender.send(items) 45 | 46 | # Check if the value sending was successful 47 | if response.failed == 0: 48 | # Print a success message along with the response time 49 | print(f"Value sent successfully in {response.time}") 50 | elif response.details: 51 | # Iterate through the list of responses from Zabbix server/proxy. 52 | for node, chunks in response.details.items(): 53 | # Iterate through the list of chunks. 54 | for resp in chunks: 55 | # Check if the value sending was successful 56 | if resp.failed == 0: 57 | # Print a success message along with the response time 58 | print(f"Value sent successfully to {node} in {resp.time}") 59 | else: 60 | # Print a failure message 61 | print(f"Failed to send value to {node} at chunk step {resp.chunk}") 62 | else: 63 | # Print a failure message 64 | print("Failed to send value") 65 | 66 | # Run the main coroutine 67 | asyncio.run(main()) 68 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/agent_config_using.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncSender 8 | 9 | 10 | async def main(): 11 | """ 12 | The main function to perform asynchronous tasks. 13 | """ 14 | 15 | # You can create an instance of AsyncSender using the default configuration file path 16 | # (typically '/etc/zabbix/zabbix_agentd.conf') 17 | # 18 | # sender = AsyncSender(use_config=True) 19 | # 20 | # Or you can create an instance of AsyncSender using a custom configuration file path 21 | sender = AsyncSender(use_config=True, config_path='/etc/zabbix/zabbix_agent2.conf') 22 | 23 | # Send a value to a Zabbix server/proxy with specified parameters 24 | # Parameters: (host, key, value, clock) 25 | response = await sender.send_value('host', 'item.key', 'value', 1695713666) 26 | 27 | # Check if the value sending was successful 28 | if response.failed == 0: 29 | # Print a success message along with the response time 30 | print(f"Value sent successfully in {response.time}") 31 | elif response.details: 32 | # Iterate through the list of responses from Zabbix server/proxy. 33 | for node, chunks in response.details.items(): 34 | # Iterate through the list of chunks. 35 | for resp in chunks: 36 | # Check if the value sending was successful 37 | if resp.failed == 0: 38 | # Print a success message along with the response time 39 | print(f"Value sent successfully to {node} in {resp.time}") 40 | else: 41 | # Print a failure message 42 | print(f"Failed to send value to {node} at chunk step {resp.chunk}") 43 | else: 44 | # Print a failure message 45 | print("Failed to send value") 46 | 47 | # Run the main coroutine 48 | asyncio.run(main()) 49 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/bulk_sending.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import ItemValue, AsyncSender 8 | 9 | 10 | # List of ItemValue instances representing items to be sent 11 | items = [ 12 | ItemValue('host1', 'item.key1', 10), 13 | ItemValue('host1', 'item.key2', 'test message'), 14 | ItemValue('host2', 'item.key1', -1, 1695713666), 15 | ItemValue('host3', 'item.key1', '{"msg":"test message"}'), 16 | ItemValue('host2', 'item.key1', 0, 1695713666, 100) 17 | ] 18 | 19 | 20 | async def main(): 21 | """ 22 | The main function to perform asynchronous tasks. 23 | """ 24 | 25 | # Create an instance of the AsyncSender class with the specified server details 26 | sender = AsyncSender("127.0.0.1", 10051) 27 | 28 | # Send multiple items to the Zabbix server/proxy and receive response 29 | response = await sender.send(items) 30 | 31 | # Check if the value sending was successful 32 | if response.failed == 0: 33 | # Print a success message along with the response time 34 | print(f"Value sent successfully in {response.time}") 35 | elif response.details: 36 | # Iterate through the list of responses from Zabbix server/proxy. 37 | for node, chunks in response.details.items(): 38 | # Iterate through the list of chunks. 39 | for resp in chunks: 40 | # Check if the value sending was successful 41 | if resp.failed == 0: 42 | # Print a success message along with the response time 43 | print(f"Value sent successfully to {node} in {resp.time}") 44 | else: 45 | # Print a failure message 46 | print(f"Failed to send value to {node} at chunk step {resp.chunk}") 47 | else: 48 | # Print a failure message 49 | print("Failed to send value") 50 | 51 | # Run the main coroutine 52 | asyncio.run(main()) 53 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/custom_source_ip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncSender 8 | 9 | 10 | async def main(): 11 | """ 12 | The main function to perform asynchronous tasks. 13 | """ 14 | 15 | # Create an instance of the AsyncSender class with specified parameters 16 | # Parameters: (server, port, source_ip) 17 | sender = AsyncSender("127.0.0.1", 10051, source_ip="10.10.1.5") 18 | 19 | # Send a value to a Zabbix server/proxy with specified parameters 20 | # Parameters: (host, key, value, clock) 21 | response = await sender.send_value('host', 'item.key', 'value', 1695713666) 22 | 23 | # Check if the value sending was successful 24 | if response.failed == 0: 25 | # Print a success message along with the response time 26 | print(f"Value sent successfully in {response.time}") 27 | else: 28 | # Print a failure message 29 | print("Failed to send value") 30 | 31 | # Run the main coroutine 32 | asyncio.run(main()) 33 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/single_sending.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import asyncio 7 | from zabbix_utils import AsyncSender 8 | 9 | 10 | # Zabbix server/proxy details for AsyncSender 11 | ZABBIX_SERVER = { 12 | "server": "127.0.0.1", # Zabbix server/proxy IP address or hostname 13 | "port": 10051 # Zabbix server/proxy port for AsyncSender 14 | } 15 | 16 | 17 | async def main(): 18 | """ 19 | The main function to perform asynchronous tasks. 20 | """ 21 | 22 | # Create an instance of the AsyncSender class with the specified server details 23 | sender = AsyncSender(**ZABBIX_SERVER) 24 | 25 | # Send a value to a Zabbix server/proxy with specified parameters 26 | # Parameters: (host, key, value, clock, ns) 27 | response = await sender.send_value('host', 'item.key', 'value', 1695713666, 30) 28 | 29 | # Check if the value sending was successful 30 | if response.failed == 0: 31 | # Print a success message along with the response time 32 | print(f"Value sent successfully in {response.time}") 33 | else: 34 | # Print a failure message 35 | print("Failed to send value") 36 | 37 | # Run the main coroutine 38 | asyncio.run(main()) 39 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/tls_cert_context.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | import asyncio 8 | from zabbix_utils import AsyncSender 9 | 10 | # Zabbix server details 11 | ZABBIX_SERVER = "zabbix-server.example.com" 12 | ZABBIX_PORT = 10051 13 | 14 | # Paths to certificate and key files 15 | CA_PATH = 'path/to/cabundle.pem' 16 | CERT_PATH = 'path/to/agent.crt' 17 | KEY_PATH = 'path/to/agent.key' 18 | 19 | 20 | # Create and configure an SSL context for secure communication with the Zabbix server. 21 | def custom_context(*args, **kwargs) -> ssl.SSLContext: 22 | 23 | # Create an SSL context for TLS client 24 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 25 | 26 | # Load the client certificate and private key 27 | context.load_cert_chain(CERT_PATH, keyfile=KEY_PATH) 28 | 29 | # Load the certificate authority bundle file 30 | context.load_verify_locations(cafile=CA_PATH) 31 | 32 | # Disable hostname verification 33 | context.check_hostname = False 34 | 35 | # Set the verification mode to require a valid certificate 36 | context.verify_mode = ssl.VerifyMode.CERT_REQUIRED 37 | 38 | # Return created context 39 | return context 40 | 41 | 42 | async def main(): 43 | """ 44 | The main function to perform asynchronous tasks. 45 | """ 46 | 47 | # Create an instance of AsyncSender with SSL context 48 | sender = AsyncSender( 49 | server=ZABBIX_SERVER, 50 | port=ZABBIX_PORT, 51 | ssl_context=custom_context 52 | ) 53 | 54 | # Send a value to a Zabbix server/proxy with specified parameters 55 | # Parameters: (host, key, value, clock, ns) 56 | response = await sender.send_value('host', 'item.key', 'value', 1695713666, 30) 57 | 58 | # Check if the value sending was successful 59 | if response.failed == 0: 60 | # Print a success message along with the response time 61 | print(f"Value sent successfully in {response.time}") 62 | else: 63 | # Print a failure message 64 | print("Failed to send value") 65 | 66 | # Run the main coroutine 67 | asyncio.run(main()) 68 | -------------------------------------------------------------------------------- /examples/sender/asynchronous/tls_cert_context_from_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | import asyncio 8 | from zabbix_utils import AsyncSender 9 | 10 | # Zabbix server details 11 | ZABBIX_SERVER = "zabbix-server.example.com" 12 | ZABBIX_PORT = 10051 13 | 14 | 15 | # Create and configure an SSL context for secure communication with the Zabbix server. 16 | def custom_context(config) -> ssl.SSLContext: 17 | 18 | # Try to get paths to certificate and key files 19 | ca_path = config.get('tlscafile') 20 | cert_path = config.get('tlscertfile') 21 | key_path = config.get('tlskeyfile') 22 | 23 | # Create an SSL context for TLS client 24 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 25 | 26 | # Load the client certificate and private key 27 | context.load_cert_chain(cert_path, keyfile=key_path) 28 | 29 | # Load the certificate authority bundle file 30 | context.load_verify_locations(cafile=ca_path) 31 | 32 | # Disable hostname verification 33 | context.check_hostname = False 34 | 35 | # Set the verification mode to require a valid certificate 36 | context.verify_mode = ssl.VerifyMode.CERT_REQUIRED 37 | 38 | # Return created context 39 | return context 40 | 41 | 42 | async def main(): 43 | """ 44 | The main function to perform asynchronous tasks. 45 | """ 46 | 47 | # Create an instance of AsyncSender with SSL context 48 | sender = AsyncSender( 49 | server=ZABBIX_SERVER, 50 | port=ZABBIX_PORT, 51 | ssl_context=custom_context 52 | ) 53 | 54 | # Send a value to a Zabbix server/proxy with specified parameters 55 | # Parameters: (host, key, value, clock, ns) 56 | response = await sender.send_value('host', 'item.key', 'value', 1695713666, 30) 57 | 58 | # Check if the value sending was successful 59 | if response.failed == 0: 60 | # Print a success message along with the response time 61 | print(f"Value sent successfully in {response.time}") 62 | else: 63 | # Print a failure message 64 | print("Failed to send value") 65 | 66 | # Run the main coroutine 67 | asyncio.run(main()) 68 | -------------------------------------------------------------------------------- /examples/sender/synchronous/agent_clusters_using.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ItemValue, Sender 7 | 8 | # You can create an instance of Sender specifying server address and port: 9 | # 10 | # sender = Sender(server='127.0.0.1', port=10051) 11 | # 12 | # Or you can create an instance of Sender specifying a list of Zabbix clusters: 13 | zabbix_clusters = [ 14 | ['zabbix.cluster1.node1', 'zabbix.cluster1.node2:10051'], 15 | ['zabbix.cluster2.node1:10051', 'zabbix.cluster2.node2:20051', 'zabbix.cluster2.node3'] 16 | ] 17 | sender = Sender(clusters=zabbix_clusters) 18 | # You can also specify Zabbix clusters at the same time with server address and port: 19 | # 20 | # sender = Sender(server='127.0.0.1', port=10051, clusters=zabbix_clusters) 21 | # 22 | # In such case, specified server address and port will be appended to the cluster list 23 | # as a cluster of a single node 24 | 25 | # List of ItemValue instances representing items to be sent 26 | items = [ 27 | ItemValue('host1', 'item.key1', 10), 28 | ItemValue('host1', 'item.key2', 'test message'), 29 | ItemValue('host2', 'item.key1', -1, 1695713666), 30 | ItemValue('host3', 'item.key1', '{"msg":"test message"}'), 31 | ItemValue('host2', 'item.key1', 0, 1695713666, 100) 32 | ] 33 | 34 | # Send multiple items to the Zabbix server/proxy and receive response 35 | response = sender.send(items) 36 | 37 | # Check if the value sending was successful 38 | if response.failed == 0: 39 | # Print a success message along with the response time 40 | print(f"Value sent successfully in {response.time}") 41 | elif response.details: 42 | # Iterate through the list of responses from Zabbix server/proxy. 43 | for node, chunks in response.details.items(): 44 | # Iterate through the list of chunks. 45 | for resp in chunks: 46 | # Check if the value sending was successful 47 | if resp.failed == 0: 48 | # Print a success message along with the response time 49 | print(f"Value sent successfully to {node} in {resp.time}") 50 | else: 51 | # Print a failure message 52 | print(f"Failed to send value to {node} at chunk step {resp.chunk}") 53 | else: 54 | # Print a failure message 55 | print("Failed to send value") 56 | -------------------------------------------------------------------------------- /examples/sender/synchronous/agent_config_using.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import Sender 7 | 8 | # You can create an instance of Sender using the default configuration file path 9 | # (typically '/etc/zabbix/zabbix_agentd.conf') 10 | # 11 | # sender = Sender(use_config=True) 12 | # 13 | # Or you can create an instance of Sender using a custom configuration file path 14 | sender = Sender(use_config=True, config_path='/etc/zabbix/zabbix_agent2.conf') 15 | 16 | # Send a value to a Zabbix server/proxy with specified parameters 17 | # Parameters: (host, key, value, clock) 18 | response = sender.send_value('host', 'item.key', 'value', 1695713666) 19 | 20 | # Check if the value sending was successful 21 | if response.failed == 0: 22 | # Print a success message along with the response time 23 | print(f"Value sent successfully in {response.time}") 24 | elif response.details: 25 | # Iterate through the list of responses from Zabbix server/proxy. 26 | for node, chunks in response.details.items(): 27 | # Iterate through the list of chunks. 28 | for resp in chunks: 29 | # Check if the value sending was successful 30 | if resp.failed == 0: 31 | # Print a success message along with the response time 32 | print(f"Value sent successfully to {node} in {resp.time}") 33 | else: 34 | # Print a failure message 35 | print(f"Failed to send value to {node} at chunk step {resp.chunk}") 36 | else: 37 | # Print a failure message 38 | print("Failed to send value") 39 | -------------------------------------------------------------------------------- /examples/sender/synchronous/bulk_sending.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import ItemValue, Sender 7 | 8 | # List of ItemValue instances representing items to be sent 9 | items = [ 10 | ItemValue('host1', 'item.key1', 10), 11 | ItemValue('host1', 'item.key2', 'test message'), 12 | ItemValue('host2', 'item.key1', -1, 1695713666), 13 | ItemValue('host3', 'item.key1', '{"msg":"test message"}'), 14 | ItemValue('host2', 'item.key1', 0, 1695713666, 100) 15 | ] 16 | 17 | # Create an instance of the Sender class with the specified server details 18 | sender = Sender("127.0.0.1", 10051) 19 | 20 | # Send multiple items to the Zabbix server/proxy and receive response 21 | response = sender.send(items) 22 | 23 | # Check if the value sending was successful 24 | if response.failed == 0: 25 | # Print a success message along with the response time 26 | print(f"Value sent successfully in {response.time}") 27 | elif response.details: 28 | # Iterate through the list of responses from Zabbix server/proxy. 29 | for node, chunks in response.details.items(): 30 | # Iterate through the list of chunks. 31 | for resp in chunks: 32 | # Check if the value sending was successful 33 | if resp.failed == 0: 34 | # Print a success message along with the response time 35 | print(f"Value sent successfully to {node} in {resp.time}") 36 | else: 37 | # Print a failure message 38 | print(f"Failed to send value to {node} at chunk step {resp.chunk}") 39 | else: 40 | # Print a failure message 41 | print("Failed to send value") 42 | -------------------------------------------------------------------------------- /examples/sender/synchronous/custom_source_ip.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import Sender 7 | 8 | # Create an instance of the Sender class with specified parameters 9 | # Parameters: (server, port, source_ip) 10 | sender = Sender("127.0.0.1", 10051, source_ip="10.10.1.5") 11 | 12 | # Send a value to a Zabbix server/proxy with specified parameters 13 | # Parameters: (host, key, value, clock) 14 | response = sender.send_value('host', 'item.key', 'value', 1695713666) 15 | 16 | # Check if the value sending was successful 17 | if response.failed == 0: 18 | # Print a success message along with the response time 19 | print(f"Value sent successfully in {response.time}") 20 | else: 21 | # Print a failure message 22 | print("Failed to send value") 23 | -------------------------------------------------------------------------------- /examples/sender/synchronous/psk_wrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | from zabbix_utils import Sender 8 | 9 | # Try importing sslpsk3, fall back to sslpsk2 if not available 10 | try: 11 | import sslpsk3 as sslpsk 12 | except ImportError: 13 | # Import sslpsk2 if sslpsk3 is not available 14 | import sslpsk2 as sslpsk 15 | 16 | 17 | # PSK wrapper function for SSL connection 18 | def psk_wrapper(sock, tls): 19 | # Pre-Shared Key (PSK) and PSK Identity 20 | psk = bytes.fromhex('608b0a0049d41fdb35a824ef0a227f24e5099c60aa935e803370a961c937d6f7') 21 | psk_identity = b'PSKID' 22 | 23 | return sslpsk.wrap_socket( 24 | sock, 25 | ssl_version=ssl.PROTOCOL_TLSv1_2, 26 | ciphers='ECDHE-PSK-AES128-CBC-SHA256', 27 | psk=(psk, psk_identity) 28 | ) 29 | 30 | 31 | # Zabbix server details 32 | ZABBIX_SERVER = "127.0.0.1" 33 | ZABBIX_PORT = 10051 34 | 35 | # Create a Sender instance with PSK support 36 | sender = Sender( 37 | server=ZABBIX_SERVER, 38 | port=ZABBIX_PORT, 39 | socket_wrapper=psk_wrapper 40 | ) 41 | 42 | # Send a value to a Zabbix server/proxy with specified parameters 43 | # Parameters: (host, key, value, clock, ns) 44 | response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) 45 | 46 | # Check if the value sending was successful 47 | if response.failed == 0: 48 | # Print a success message along with the response time 49 | print(f"Value sent successfully in {response.time}") 50 | else: 51 | # Print a failure message 52 | print("Failed to send value") 53 | -------------------------------------------------------------------------------- /examples/sender/synchronous/psk_wrapper_from_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | from zabbix_utils import Sender 8 | 9 | # Try importing sslpsk3, fall back to sslpsk2 if not available 10 | try: 11 | import sslpsk3 as sslpsk 12 | except ImportError: 13 | # Import sslpsk2 if sslpsk3 is not available 14 | import sslpsk2 as sslpsk 15 | 16 | 17 | # PSK wrapper function for SSL connection 18 | def psk_wrapper(sock, config): 19 | psk = None 20 | psk_identity = config.get('tlspskidentity').encode('utf-8') 21 | psk_file = config.get('tlspskfile') 22 | 23 | # Read PSK from file if specified 24 | if psk_file: 25 | with open(psk_file, encoding='utf-8') as f: 26 | psk = bytes.fromhex(f.read()) 27 | 28 | # Check if both PSK and PSK identity are available 29 | if psk and psk_identity: 30 | return sslpsk.wrap_socket( 31 | sock, 32 | ssl_version=ssl.PROTOCOL_TLSv1_2, 33 | ciphers='ECDHE-PSK-AES128-CBC-SHA256', 34 | psk=(psk, psk_identity) 35 | ) 36 | 37 | # Return original socket if PSK or PSK identity is missing 38 | return sock 39 | 40 | 41 | # Create a Sender instance with PSK support 42 | sender = Sender(use_config=True, socket_wrapper=psk_wrapper) 43 | 44 | # Send a value to a Zabbix server/proxy with specified parameters 45 | # Parameters: (host, key, value, clock, ns) 46 | response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) 47 | 48 | # Check if the value sending was successful 49 | if response.failed == 0: 50 | # Print a success message along with the response time 51 | print(f"Value sent successfully in {response.time}") 52 | else: 53 | # Print a failure message 54 | print("Failed to send value") 55 | -------------------------------------------------------------------------------- /examples/sender/synchronous/single_sending.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | from zabbix_utils import Sender 7 | 8 | # Zabbix server/proxy details for Sender 9 | ZABBIX_SERVER = { 10 | "server": "127.0.0.1", # Zabbix server/proxy IP address or hostname 11 | "port": 10051 # Zabbix server/proxy port for Sender 12 | } 13 | 14 | # Create an instance of the Sender class with the specified server details 15 | sender = Sender(**ZABBIX_SERVER) 16 | 17 | # Send a value to a Zabbix server/proxy with specified parameters 18 | # Parameters: (host, key, value, clock, ns) 19 | response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) 20 | 21 | # Check if the value sending was successful 22 | if response.failed == 0: 23 | # Print a success message along with the response time 24 | print(f"Value sent successfully in {response.time}") 25 | else: 26 | # Print a failure message 27 | print("Failed to send value") 28 | -------------------------------------------------------------------------------- /examples/sender/synchronous/tls_cert_wrapper.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | from zabbix_utils import Sender 8 | 9 | # Zabbix server details 10 | ZABBIX_SERVER = "zabbix-server.example.com" 11 | ZABBIX_PORT = 10051 12 | 13 | # Paths to certificate and key files 14 | CA_PATH = 'path/to/cabundle.pem' 15 | CERT_PATH = 'path/to/agent.crt' 16 | KEY_PATH = 'path/to/agent.key' 17 | 18 | 19 | # Define a function for wrapping the socket with TLS 20 | def tls_wrapper(sock, *args, **kwargs): 21 | 22 | # Create an SSL context for TLS client 23 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 24 | 25 | # Load the client certificate and private key 26 | context.load_cert_chain(CERT_PATH, keyfile=KEY_PATH) 27 | 28 | # Load the certificate authority bundle file 29 | context.load_verify_locations(cafile=CA_PATH) 30 | 31 | # Disable hostname verification 32 | context.check_hostname = False 33 | 34 | # Set the verification mode to require a valid certificate 35 | context.verify_mode = ssl.VerifyMode.CERT_REQUIRED 36 | 37 | # Wrap the socket with TLS using the created context 38 | return context.wrap_socket(sock, server_hostname=ZABBIX_SERVER) 39 | 40 | 41 | # Create an instance of Sender with TLS configuration 42 | sender = Sender( 43 | server=ZABBIX_SERVER, 44 | port=ZABBIX_PORT, 45 | # Use the defined tls_wrapper function for socket wrapping 46 | socket_wrapper=tls_wrapper 47 | ) 48 | 49 | # Send a value to a Zabbix server/proxy with specified parameters 50 | # Parameters: (host, key, value, clock, ns) 51 | response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) 52 | 53 | # Check if the value sending was successful 54 | if response.failed == 0: 55 | # Print a success message along with the response time 56 | print(f"Value sent successfully in {response.time}") 57 | else: 58 | # Print a failure message 59 | print("Failed to send value") 60 | -------------------------------------------------------------------------------- /examples/sender/synchronous/tls_cert_wrapper_from_config.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2001-2023 Zabbix SIA 2 | # 3 | # Zabbix SIA licenses this file to you under the MIT License. 4 | # See the LICENSE file in the project root for more information. 5 | 6 | import ssl 7 | from zabbix_utils import Sender 8 | 9 | # Zabbix server details 10 | ZABBIX_SERVER = "zabbix-server.example.com" 11 | ZABBIX_PORT = 10051 12 | 13 | 14 | # Define a function for wrapping the socket with TLS 15 | def tls_wrapper(sock, config): 16 | 17 | # Try to get paths to certificate and key files 18 | ca_path = config.get('tlscafile') 19 | cert_path = config.get('tlscertfile') 20 | key_path = config.get('tlskeyfile') 21 | 22 | # Create an SSL context for TLS client 23 | context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 24 | 25 | # Load the client certificate and private key 26 | context.load_cert_chain(cert_path, keyfile=key_path) 27 | 28 | # Load the certificate authority bundle file 29 | context.load_verify_locations(cafile=ca_path) 30 | 31 | # Disable hostname verification 32 | context.check_hostname = False 33 | 34 | # Set the verification mode to require a valid certificate 35 | context.verify_mode = ssl.VerifyMode.CERT_REQUIRED 36 | 37 | # Wrap the socket with TLS using the created context 38 | return context.wrap_socket(sock, server_hostname=ZABBIX_SERVER) 39 | 40 | 41 | # Create an instance of Sender with TLS configuration 42 | sender = Sender( 43 | server=ZABBIX_SERVER, 44 | port=ZABBIX_PORT, 45 | # Use the defined tls_wrapper function for socket wrapping 46 | socket_wrapper=tls_wrapper 47 | ) 48 | 49 | # Send a value to a Zabbix server/proxy with specified parameters 50 | # Parameters: (host, key, value, clock, ns) 51 | response = sender.send_value('host', 'item.key', 'value', 1695713666, 30) 52 | 53 | # Check if the value sending was successful 54 | if response.failed == 0: 55 | # Print a success message along with the response time 56 | print(f"Value sent successfully in {response.time}") 57 | else: 58 | # Print a failure message 59 | print("Failed to send value") 60 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | flake8>=3.0.0 2 | coverage 3 | aiohttp[speedups]>=3,<4 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp[speedups]>=3,<4 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 99 3 | exclude = tests/*,setup.py -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import re 26 | import setuptools 27 | from zabbix_utils.version import __version__ 28 | 29 | with open("README.md", "r", encoding="utf-8") as fh: 30 | regexp = r'(?<=##)\s*Get started\n*(^\*.*\n){1,10}\n*##' 31 | long_description = re.sub(regexp, '', fh.read(), flags=re.M) 32 | 33 | setuptools.setup( 34 | name="zabbix_utils", 35 | version=__version__, 36 | author="Zabbix SIA", 37 | author_email="integrationteam@zabbix.com", 38 | maintainer="Aleksandr Iantsen", 39 | maintainer_email="aleksandr.iantsen@zabbix.com", 40 | description="A library with modules for working with Zabbix (Zabbix API, Zabbix sender, Zabbix get)", 41 | long_description=long_description, 42 | long_description_content_type="text/markdown", 43 | keywords="monitoring zabbix api sender get utils tools", 44 | url="https://github.com/zabbix/python-zabbix-utils", 45 | test_suite='tests', 46 | packages=["zabbix_utils"], 47 | tests_require=["unittest"], 48 | install_requires=[], 49 | extras_require={ 50 | "async": ["aiohttp>=3,<4"], 51 | }, 52 | python_requires='>=3.8', 53 | project_urls={ 54 | 'Zabbix': 'https://www.zabbix.com/documentation/current', 55 | 'Source': 'https://github.com/zabbix/python-zabbix-utils', 56 | 'Changes': 'https://github.com/zabbix/python-zabbix-utils/blob/main/CHANGELOG.md', 57 | 'Bug Tracker': 'https://github.com/zabbix/python-zabbix-utils/issues' 58 | }, 59 | classifiers=[ 60 | "Development Status :: 5 - Production/Stable", 61 | "Programming Language :: Python", 62 | "Programming Language :: Python :: 3", 63 | "Programming Language :: Python :: 3.8", 64 | "Programming Language :: Python :: 3.9", 65 | "Programming Language :: Python :: 3.10", 66 | "Programming Language :: Python :: 3.11", 67 | "Programming Language :: Python :: 3.12", 68 | "License :: OSI Approved :: MIT License", 69 | "Operating System :: OS Independent", 70 | "Topic :: System :: Monitoring", 71 | "Topic :: System :: Networking :: Monitoring", 72 | "Topic :: System :: Systems Administration", 73 | "Intended Audience :: Developers", 74 | "Intended Audience :: System Administrators", 75 | "Intended Audience :: Information Technology" 76 | ] 77 | ) 78 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zabbix/python-zabbix-utils/8d57619c96cafa56990e71c0fd65ba9115507c1d/tests/__init__.py -------------------------------------------------------------------------------- /tests/common.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import ssl 26 | import json 27 | 28 | from zabbix_utils.types import ItemValue 29 | from zabbix_utils.version import __min_supported__, __max_supported__ 30 | 31 | 32 | API_DEFAULTS = { 33 | 'user': 'Admin', 34 | 'password': 'zabbix', 35 | 'token': 'oTmtWu', 36 | 'session': 'cc364fb50199c5e305aa91785b7e49a0', 37 | 'max_version': "{}.0".format(__max_supported__ + .2), 38 | 'min_version': "{}.0".format(__min_supported__ - .2) 39 | } 40 | 41 | 42 | GETTER_DEFAULTS = { 43 | 'host': 'localhost', 44 | 'port': 10050, 45 | 'source_ip': '192.168.1.1' 46 | } 47 | 48 | SENDER_DEFAULTS = { 49 | 'server': 'localhost', 50 | 'port': 10051, 51 | 'source_ip': '192.168.1.1', 52 | 'clusters': [ 53 | ['zabbix.cluster.node1','zabbix.cluster.node2:20051'], 54 | ['zabbix.cluster2.node1','zabbix.cluster2.node2'], 55 | ['zabbix.domain'] 56 | ] 57 | } 58 | 59 | ZABBIX_CONFIG = [ 60 | f"""[root] 61 | ServerActive=zabbix.cluster.node1;zabbix.cluster.node2:20051,zabbix.cluster2.node1;zabbix.cluster2.node2,zabbix.domain 62 | Server={SENDER_DEFAULTS['server']} 63 | SourceIP={SENDER_DEFAULTS['source_ip']} 64 | TLSConnect=unencrypted 65 | TLSAccept=unencrypted 66 | """, 67 | f"""[root] 68 | Server={SENDER_DEFAULTS['server']} 69 | SourceIP={SENDER_DEFAULTS['source_ip']} 70 | """, 71 | f"""[root] 72 | SourceIP={SENDER_DEFAULTS['source_ip']} 73 | """ 74 | ] 75 | 76 | 77 | class MockBasicAuth(): 78 | login = API_DEFAULTS['user'] 79 | password = API_DEFAULTS['password'] 80 | 81 | class MockSessionConn(): 82 | def __init__(self): 83 | self._ssl = None 84 | self.closed = False 85 | def close(self): 86 | self.closed = True 87 | 88 | class MockSession(): 89 | def __init__(self, exception=None): 90 | self._default_auth = None 91 | self._connector = MockSessionConn() 92 | self.EXC = exception 93 | def set_auth(self): 94 | self._default_auth = MockBasicAuth() 95 | def del_auth(self): 96 | self._default_auth = None 97 | def set_ssl(self, ssl): 98 | self._connector._ssl = ssl 99 | def del_ssl(self): 100 | self._connector._ssl = None 101 | def set_exception(self, exception): 102 | self.EXC = exception 103 | def del_exception(self): 104 | self.EXC = None 105 | async def close(self): 106 | pass 107 | async def post(self, *args, **kwargs): 108 | if self.EXC: 109 | raise self.EXC() 110 | return MockAPIResponse() 111 | 112 | 113 | class MockAPIResponse(): 114 | def __init__(self, exception=None): 115 | self.EXC = exception 116 | def set_exception(self, exception): 117 | self.EXC = exception 118 | def del_exception(self): 119 | self.EXC = None 120 | def raise_for_status(self): 121 | pass 122 | async def json(self, *args, **kwargs): 123 | if self.EXC: 124 | raise self.EXC() 125 | return { 126 | "jsonrpc": "2.0", 127 | "result": "{}.0".format(__max_supported__), 128 | "id": "0" 129 | } 130 | def read(self, *args, **kwargs): 131 | if self.EXC: 132 | raise self.EXC() 133 | return json.dumps({ 134 | "jsonrpc": "2.0", 135 | "result": "{}.0".format(__max_supported__), 136 | "id": "0" 137 | }).encode('utf-8') 138 | 139 | 140 | class MockConnector(): 141 | def __init__(self, input_stream, exception=None): 142 | self.STREAM = input_stream 143 | self.EXC = exception 144 | def __raiser(self, *args, **kwargs): 145 | if self.EXC: 146 | raise self.EXC() 147 | def connect(self, *args, **kwargs): 148 | self.__raiser(*args, **kwargs) 149 | def recv(self, bufsize, *args, **kwargs): 150 | self.__raiser(*args, **kwargs) 151 | resp = self.STREAM[0:bufsize] 152 | self.STREAM = self.STREAM[bufsize:] 153 | return resp 154 | def sendall(self, *args, **kwargs): 155 | self.__raiser(*args, **kwargs) 156 | 157 | 158 | class MockReader(): 159 | STREAM = '' 160 | EXC = None 161 | @classmethod 162 | def set_stream(cls, stream): 163 | cls.STREAM = stream 164 | @classmethod 165 | def set_exception(cls, exception): 166 | cls.EXC = exception 167 | @classmethod 168 | async def readexactly(cls, length=0): 169 | if cls.EXC: 170 | raise cls.EXC() 171 | resp = cls.STREAM[0:length] 172 | cls.STREAM = cls.STREAM[length:] 173 | return resp 174 | @classmethod 175 | def close(cls): 176 | cls.EXC = None 177 | 178 | 179 | class MockWriter(): 180 | EXC = None 181 | @classmethod 182 | def set_exception(cls, exception): 183 | cls.EXC = exception 184 | @classmethod 185 | def write(cls, *args, **kwargs): 186 | if cls.EXC: 187 | raise cls.EXC() 188 | @classmethod 189 | async def drain(cls, *args, **kwargs): 190 | pass 191 | @classmethod 192 | def close(cls): 193 | cls.EXC = None 194 | @classmethod 195 | async def wait_closed(cls): 196 | cls.EXC = None 197 | 198 | class MockLogger(): 199 | def debug(self, *args, **kwargs): 200 | pass 201 | def error(self, *args, **kwargs): 202 | pass 203 | def warning(self, *args, **kwargs): 204 | pass 205 | 206 | def mock_send_sync_request(self, method, *args, **kwargs): 207 | result = {} 208 | if method == 'apiinfo.version': 209 | result = f"{__max_supported__}.0" 210 | elif method == 'user.login': 211 | result = API_DEFAULTS['session'] 212 | elif method == 'user.logout': 213 | result = True 214 | elif method == 'user.checkAuthentication': 215 | result = {'userid': 42} 216 | return {'jsonrpc': '2.0', 'result': result, 'id': 1} 217 | 218 | async def mock_send_async_request(self, method, *args, **kwargs): 219 | result = {} 220 | if method == 'user.login': 221 | result = API_DEFAULTS['session'] 222 | elif method == 'user.logout': 223 | result = True 224 | elif method == 'user.checkAuthentication': 225 | result = {'userid': 42} 226 | return {'jsonrpc': '2.0', 'result': result, 'id': 1} 227 | 228 | def socket_wrapper(connection, *args, **kwargs): 229 | return connection 230 | 231 | def ssl_context(*args, **kwargs): 232 | return ssl.create_default_context() 233 | 234 | def response_gen(items): 235 | def items_check(items): 236 | for i, item in enumerate(items): 237 | if isinstance(item, ItemValue): 238 | items[i] = item.to_json() 239 | return items 240 | info = { 241 | 'processed': len([i for i in items_check(items) if json.loads(i['value'])]), 242 | 'failed': len([i for i in items_check(items) if not json.loads(i['value'])]), 243 | 'total': len(items), 244 | 'seconds spent': '0.000100' 245 | } 246 | result = { 247 | 'response': 'success', 248 | 'info': '; '.join([f"{k}: {v}" for k,v in info.items()]) 249 | } 250 | 251 | return result 252 | -------------------------------------------------------------------------------- /tests/test_zabbix_aiogetter.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import json 26 | import socket 27 | import asyncio 28 | import unittest 29 | 30 | from tests import common 31 | from zabbix_utils import AsyncGetter 32 | from zabbix_utils import ProcessingError 33 | 34 | 35 | DEFAULT_VALUES = common.GETTER_DEFAULTS 36 | 37 | 38 | class TestAsyncGetter(unittest.IsolatedAsyncioTestCase): 39 | """Test cases for AsyncGetter object""" 40 | 41 | def test_init(self): 42 | """Tests creating of AsyncGetter object""" 43 | 44 | test_cases = [ 45 | { 46 | 'input': {'source_ip': DEFAULT_VALUES['source_ip'], 'timeout': 20}, 47 | 'output': json.dumps({ 48 | "host": "127.0.0.1", "port": DEFAULT_VALUES['port'], "timeout": 20, "source_ip": DEFAULT_VALUES['source_ip'], "ssl_context": None 49 | }) 50 | }, 51 | { 52 | 'input': {'host':DEFAULT_VALUES['host']}, 53 | 'output': json.dumps({ 54 | "host": DEFAULT_VALUES['host'], "port": DEFAULT_VALUES['port'], "timeout": 10, "source_ip": None, "ssl_context": None 55 | }) 56 | }, 57 | { 58 | 'input': {'host':DEFAULT_VALUES['host'], 'port': 10150}, 59 | 'output': json.dumps({ 60 | "host": DEFAULT_VALUES['host'], "port": 10150, "timeout": 10, "source_ip": None, "ssl_context": None 61 | }) 62 | } 63 | ] 64 | 65 | for case in test_cases: 66 | 67 | agent = AsyncGetter(**case['input']) 68 | 69 | self.assertEqual(json.dumps(agent.__dict__), case['output'], 70 | f"unexpected output with input data: {case['input']}") 71 | 72 | with self.assertRaises(TypeError, 73 | msg="expected TypeError exception hasn't been raised"): 74 | agent = AsyncGetter(ssl_context='wrapper', **case['input']) 75 | 76 | async def test_get_response(self): 77 | """Tests __get_response method in different cases""" 78 | 79 | async def test_case(input_stream): 80 | getter = AsyncGetter() 81 | reader = common.MockReader() 82 | reader.set_stream(input_stream) 83 | return await getter._AsyncGetter__get_response(reader) 84 | 85 | test_cases = [ 86 | {'input': b'ZBXD\x01\x04\x00\x00\x00\x04\x00\x00\x00test', 'output': 'test'}, 87 | { 88 | 'input': b'ZBXD\x01\x14\x00\x00\x00\x00\x00\x00\x00test_creating_packet', 89 | 'output': 'test_creating_packet' 90 | }, 91 | { 92 | 'input': b'ZBXD\x03\x1d\x00\x00\x00\x15\x00\x00\x00x\x9c+I-.\x89O\xce\xcf-(J-.\xce\xcc\xcf\x8bO\xcbIL\x07\x00a\xd1\x08\xcb', 93 | 'output': 'test_compression_flag' 94 | } 95 | ] 96 | 97 | for case in test_cases: 98 | self.assertEqual(await test_case(case['input']), case['output'], 99 | f"unexpected output with input data: {case['input']}") 100 | 101 | with self.assertRaises(ProcessingError, 102 | msg="expected ProcessingError exception hasn't been raised"): 103 | await test_case(b'test') 104 | 105 | with self.assertRaises(ProcessingError, 106 | msg="expected ProcessingError exception hasn't been raised"): 107 | await test_case(b'ZBXD\x04\x04\x00\x00\x00\x00\x00\x00\x00test') 108 | 109 | with self.assertRaises(ProcessingError, 110 | msg="expected ProcessingError exception hasn't been raised"): 111 | await test_case(b'ZBXD\x00\x04\x00\x00\x00\x00\x00\x00\x00test') 112 | 113 | async def test_get(self): 114 | """Tests get() method in different cases""" 115 | 116 | output = 'test_response' 117 | response = b'ZBXD\x01\r\x00\x00\x00\x00\x00\x00\x00' + output.encode('utf-8') 118 | 119 | test_cases = [ 120 | { 121 | 'connection': {'input_stream': response}, 122 | 'input': {}, 123 | 'output': output, 124 | 'raised': False 125 | }, 126 | { 127 | 'connection': {'input_stream': response}, 128 | 'input': {'source_ip': DEFAULT_VALUES['source_ip']}, 129 | 'output': output, 130 | 'raised': False 131 | }, 132 | { 133 | 'connection': {'input_stream': response}, 134 | 'input': {'ssl_context': common.ssl_context}, 135 | 'output': output, 136 | 'raised': False 137 | }, 138 | { 139 | 'connection': {'input_stream': response, 'exception': TypeError}, 140 | 'input': {'ssl_context': lambda: ''}, 141 | 'output': output, 142 | 'raised': True 143 | }, 144 | { 145 | 'connection': {'input_stream': response, 'exception': ConnectionResetError}, 146 | 'input': {}, 147 | 'output': output, 148 | 'raised': True 149 | }, 150 | { 151 | 'connection': {'input_stream': response, 'exception': socket.error}, 152 | 'input': {}, 153 | 'output': output, 154 | 'raised': True 155 | }, 156 | { 157 | 'connection': {'input_stream': response, 'exception': socket.gaierror}, 158 | 'input': {}, 159 | 'output': output, 160 | 'raised': True 161 | }, 162 | { 163 | 'connection': {'input_stream': response, 'exception': asyncio.TimeoutError}, 164 | 'input': {}, 165 | 'output': output, 166 | 'raised': True 167 | } 168 | ] 169 | 170 | for case in test_cases: 171 | 172 | async def mock_open_connection(*args, **kwargs): 173 | reader = common.MockReader() 174 | reader.set_stream(case['connection'].get('input_stream','')) 175 | writer = common.MockWriter() 176 | writer.set_exception(case['connection'].get('exception')) 177 | return reader, writer 178 | 179 | with unittest.mock.patch.multiple( 180 | asyncio, 181 | open_connection=mock_open_connection): 182 | 183 | try: 184 | getter = AsyncGetter(**case['input']) 185 | except case['connection'].get('exception', Exception): 186 | if not case['raised']: 187 | self.fail(f"raised unexpected Exception with input data: {case['input']}") 188 | 189 | try: 190 | resp = await getter.get('system.uname') 191 | except case['connection'].get('exception', Exception): 192 | if not case['raised']: 193 | self.fail(f"raised unexpected Exception with input data: {case['input']}") 194 | else: 195 | self.assertEqual(resp.value, case['output'], 196 | f"unexpected output with input data: {case['input']}") 197 | 198 | 199 | if __name__ == '__main__': 200 | unittest.main() 201 | -------------------------------------------------------------------------------- /tests/test_zabbix_common.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import unittest 26 | 27 | from zabbix_utils.common import ModuleUtils, ZabbixProtocol 28 | 29 | 30 | class TestModuleUtils(unittest.TestCase): 31 | """Test cases for ModuleUtils class""" 32 | 33 | def test_check_url(self): 34 | """Tests check_url method in different cases""" 35 | 36 | filename = ModuleUtils.JSONRPC_FILE 37 | 38 | test_cases = [ 39 | {'input': '127.0.0.1', 'output': f"http://127.0.0.1/{filename}"}, 40 | {'input': 'https://localhost', 'output': f"https://localhost/{filename}"}, 41 | {'input': 'localhost/zabbix', 'output': f"http://localhost/zabbix/{filename}"}, 42 | {'input': 'localhost/', 'output': f"http://localhost/{filename}"}, 43 | {'input': f"127.0.0.1/{filename}", 'output': f"http://127.0.0.1/{filename}"} 44 | ] 45 | 46 | for case in test_cases: 47 | result = ModuleUtils.check_url(case['input']) 48 | self.assertEqual(result, case['output'], 49 | f"unexpected output with input data: {case['input']}") 50 | 51 | def test_mask_secret(self): 52 | """Tests mask_secret method in different cases""" 53 | 54 | mask = ModuleUtils.HIDING_MASK 55 | 56 | test_cases = [ 57 | {'input': {'string': 'lZSwaQ', 'show_len': 5}, 'output': mask}, 58 | {'input': {'string': 'ZWvaGS5SzNGaR990f', 'show_len': 4}, 'output': f"ZWva{mask}990f"}, 59 | {'input': {'string': 'KZneJzgRzdlWcUjJj', 'show_len': 10}, 'output': mask}, 60 | {'input': {'string': 'g5imzEr7TPcBG47fa', 'show_len': 20}, 'output': mask}, 61 | {'input': {'string': 'In8y4eGughjBNSqEGPcqzejToVUT3OA4q5', 'show_len':2}, 'output': f"In{mask}q5"}, 62 | {'input': {'string': 'Z8pZom5EVbRZ0W5wz', 'show_len':0}, 'output': mask} 63 | ] 64 | 65 | for case in test_cases: 66 | result = ModuleUtils.mask_secret(**case['input']) 67 | self.assertEqual(result, case['output'], 68 | f"unexpected output with input data: {case['input']}") 69 | 70 | def test_hide_private(self): 71 | """Tests hide_private method in different cases""" 72 | 73 | mask = ModuleUtils.HIDING_MASK 74 | 75 | test_cases = [ 76 | { 77 | 'input': [{"auth": "q2BTIw85kqmjtXl3","token": "jZAC51wHuWdwvQnx"}], 78 | 'output': {"auth": mask, "token": mask} 79 | }, 80 | { 81 | 'input': [{"token": "jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"}], 82 | 'output': {"token": f"jZAC{mask}R2uW"} 83 | }, 84 | { 85 | 'input': [{"auth": "q2BTIw85kqmjtXl3zCgSSR26gwCGVFMK"}], 86 | 'output': {"auth": f"q2BT{mask}VFMK"} 87 | }, 88 | { 89 | 'input': [{"sessionid": "p1xqXSf2HhYWa2ml6R5R2uWwbP2T55vh"}], 90 | 'output': {"sessionid": f"p1xq{mask}55vh"} 91 | }, 92 | { 93 | 'input': [{"password": "HlphkcKgQKvofQHP"}], 94 | 'output': {"password": mask} 95 | }, 96 | { 97 | 'input': [{"result": "p1xqXSf2HhYWa2ml6R5R2uWwbP2T55vh"}], 98 | 'output': {"result": f"p1xq{mask}55vh"} 99 | }, 100 | { 101 | 'input': [{"result": "6.0.0"}], 102 | 'output': {"result": "6.0.0"} 103 | }, 104 | { 105 | 'input': [{"result": ["10"]}], 106 | 'output': {"result": ["10"]} 107 | }, 108 | { 109 | 'input': [{"result": [{"token": "jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"}]}], 110 | 'output': {"result": [{"token": f"jZAC{mask}R2uW"}]} 111 | }, 112 | { 113 | 'input': [{"result": [["10"],["15"]]}], 114 | 'output': {"result": [["10"],["15"]]} 115 | }, 116 | { 117 | 'input': [{"result": [[{"token": "jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"}]]}], 118 | 'output': {"result": [[{"token": f"jZAC{mask}R2uW"}]]} 119 | }, 120 | { 121 | 'input': [{"result": ["jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"]}], 122 | 'output': {"result": [f"jZAC{mask}R2uW"]} 123 | }, 124 | { 125 | 'input': [{"result": {"passwords": ["HlphkcKgQKvofQHP"]}}], 126 | 'output': {"result": {"passwords": [mask]}} 127 | }, 128 | { 129 | 'input': [{"result": {"passwords": ["HlphkcKgQKvofQHP"]}}, {}], 130 | 'output': {"result": {"passwords": ["HlphkcKgQKvofQHP"]}} 131 | }, 132 | { 133 | 'input': [{"result": {"tokens": ["jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"]}}], 134 | 'output': {"result": {"tokens": [f"jZAC{mask}R2uW"]}} 135 | }, 136 | { 137 | 'input': [{"result": ["jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"]}, {}], 138 | 'output': {"result": [f"jZAC51wHuWdwvQnxwbP2T55vh6R5R2uW"]} 139 | } 140 | ] 141 | 142 | for case in test_cases: 143 | result = ModuleUtils.hide_private(*case['input']) 144 | self.assertEqual(result, case['output'], 145 | f"unexpected output with input data: {case['input']}") 146 | 147 | 148 | class TestZabbixProtocol(unittest.TestCase): 149 | """Test cases for ZabbixProtocol object""" 150 | 151 | def test_create_packet(self): 152 | """Tests create_packet method in different cases""" 153 | 154 | class Logger(): 155 | def debug(self, *args, **kwargs): 156 | pass 157 | 158 | test_cases = [ 159 | { 160 | 'input': {'payload':'test', 'log':Logger()}, 161 | 'output': b'ZBXD\x01\x04\x00\x00\x00\x00\x00\x00\x00test' 162 | }, 163 | { 164 | 'input': {'payload':'test_creating_packet', 'log':Logger()}, 165 | 'output': b'ZBXD\x01\x14\x00\x00\x00\x00\x00\x00\x00test_creating_packet' 166 | }, 167 | { 168 | 'input': {'payload':'test_compression_flag', 'log':Logger()}, 169 | 'output': b'ZBXD\x01\x15\x00\x00\x00\x00\x00\x00\x00test_compression_flag' 170 | }, 171 | { 172 | 'input': {'payload':'glāžšķūņu rūķīši', 'log':Logger()}, 173 | 'output': b'ZBXD\x01\x1a\x00\x00\x00\x00\x00\x00\x00gl\xc4\x81\xc5\xbe\xc5\xa1\xc4\xb7\xc5\xab\xc5\x86u r\xc5\xab\xc4\xb7\xc4\xab\xc5\xa1i' 174 | } 175 | ] 176 | 177 | for case in test_cases: 178 | resp = ZabbixProtocol.create_packet(**case['input']) 179 | self.assertEqual(resp, case['output'], 180 | f"unexpected output with input data: {case['input']}") 181 | 182 | 183 | if __name__ == '__main__': 184 | unittest.main() -------------------------------------------------------------------------------- /tests/test_zabbix_getter.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import json 26 | import socket 27 | import unittest 28 | 29 | from tests import common 30 | from zabbix_utils import Getter 31 | from zabbix_utils import ProcessingError 32 | 33 | 34 | DEFAULT_VALUES = common.GETTER_DEFAULTS 35 | 36 | 37 | class TestGetter(unittest.TestCase): 38 | """Test cases for Getter object""" 39 | 40 | def test_init(self): 41 | """Tests creating of Getter object""" 42 | 43 | test_cases = [ 44 | { 45 | 'input': {'source_ip': DEFAULT_VALUES['source_ip'], 'timeout': 20}, 46 | 'output': json.dumps({ 47 | "host": "127.0.0.1", "port": DEFAULT_VALUES['port'], "timeout": 20, "use_ipv6": False, "source_ip": DEFAULT_VALUES['source_ip'], "socket_wrapper": None 48 | }) 49 | }, 50 | { 51 | 'input': {'host':DEFAULT_VALUES['host']}, 52 | 'output': json.dumps({ 53 | "host": DEFAULT_VALUES['host'], "port": DEFAULT_VALUES['port'], "timeout": 10, "use_ipv6": False, "source_ip": None, "socket_wrapper": None 54 | }) 55 | }, 56 | { 57 | 'input': {'host':DEFAULT_VALUES['host'], 'port': 10150}, 58 | 'output': json.dumps({ 59 | "host": DEFAULT_VALUES['host'], "port": 10150, "timeout": 10, "use_ipv6": False, "source_ip": None, "socket_wrapper": None 60 | }) 61 | } 62 | ] 63 | 64 | for case in test_cases: 65 | 66 | agent = Getter(**case['input']) 67 | 68 | self.assertEqual(json.dumps(agent.__dict__), case['output'], 69 | f"unexpected output with input data: {case['input']}") 70 | 71 | with self.assertRaises(TypeError, 72 | msg="expected TypeError exception hasn't been raised"): 73 | agent = Getter(socket_wrapper='wrapper', **case['input']) 74 | 75 | def test_get_response(self): 76 | """Tests __get_response method in different cases""" 77 | 78 | test_cases = [ 79 | {'input': b'ZBXD\x01\x04\x00\x00\x00\x04\x00\x00\x00test', 'output': 'test'}, 80 | { 81 | 'input': b'ZBXD\x01\x14\x00\x00\x00\x00\x00\x00\x00test_creating_packet', 82 | 'output': 'test_creating_packet' 83 | }, 84 | { 85 | 'input': b'ZBXD\x03\x1d\x00\x00\x00\x15\x00\x00\x00x\x9c+I-.\x89O\xce\xcf-(J-.\xce\xcc\xcf\x8bO\xcbIL\x07\x00a\xd1\x08\xcb', 86 | 'output': 'test_compression_flag' 87 | } 88 | ] 89 | 90 | for case in test_cases: 91 | 92 | getter = Getter() 93 | conn = common.MockConnector(case['input']) 94 | 95 | self.assertEqual(getter._Getter__get_response(conn), case['output'], 96 | f"unexpected output with input data: {case['input']}") 97 | 98 | with self.assertRaises(ProcessingError, 99 | msg="expected ProcessingError exception hasn't been raised"): 100 | getter = Getter() 101 | conn = common.MockConnector(b'test') 102 | getter._Getter__get_response(conn) 103 | 104 | with self.assertRaises(ProcessingError, 105 | msg="expected ProcessingError exception hasn't been raised"): 106 | getter = Getter() 107 | conn = common.MockConnector(b'ZBXD\x04\x04\x00\x00\x00\x00\x00\x00\x00test') 108 | getter._Getter__get_response(conn) 109 | 110 | with self.assertRaises(ProcessingError, 111 | msg="expected ProcessingError exception hasn't been raised"): 112 | getter = Getter() 113 | conn = common.MockConnector(b'ZBXD\x00\x04\x00\x00\x00\x00\x00\x00\x00test') 114 | getter._Getter__get_response(conn) 115 | 116 | def test_get(self): 117 | """Tests get() method in different cases""" 118 | 119 | output = 'test_response' 120 | response = b'ZBXD\x01\r\x00\x00\x00\x00\x00\x00\x00' + output.encode('utf-8') 121 | 122 | test_cases = [ 123 | { 124 | 'connection': {'input_stream': response}, 125 | 'input': {'use_ipv6': False}, 126 | 'output': output, 127 | 'raised': False 128 | }, 129 | { 130 | 'connection': {'input_stream': response}, 131 | 'input': {'use_ipv6': True}, 132 | 'output': output, 133 | 'raised': False 134 | }, 135 | { 136 | 'connection': {'input_stream': response}, 137 | 'input': {'source_ip': '127.0.0.1'}, 138 | 'output': output, 139 | 'raised': False 140 | }, 141 | { 142 | 'connection': {'input_stream': response}, 143 | 'input': {'socket_wrapper': common.socket_wrapper}, 144 | 'output': output, 145 | 'raised': False 146 | }, 147 | { 148 | 'connection': {'input_stream': response, 'exception': socket.error}, 149 | 'input': {}, 150 | 'output': output, 151 | 'raised': True 152 | }, 153 | { 154 | 'connection': {'input_stream': response, 'exception': socket.gaierror}, 155 | 'input': {}, 156 | 'output': output, 157 | 'raised': True 158 | }, 159 | { 160 | 'connection': {'input_stream': response, 'exception': socket.timeout}, 161 | 'input': {}, 162 | 'output': output, 163 | 'raised': True 164 | } 165 | ] 166 | 167 | for case in test_cases: 168 | with unittest.mock.patch('socket.socket') as mock_socket: 169 | test_connector = common.MockConnector(**case['connection']) 170 | mock_socket.return_value.recv = test_connector.recv 171 | mock_socket.return_value.sendall = test_connector.sendall 172 | getter = Getter(**case['input']) 173 | 174 | try: 175 | resp = getter.get('system.uname') 176 | except case['connection'].get('exception', Exception): 177 | if not case['raised']: 178 | self.fail(f"raised unexpected Exception with input data: {case['input']}") 179 | else: 180 | self.assertEqual(resp.value, case['output'], 181 | f"unexpected output with input data: {case['input']}") 182 | 183 | 184 | if __name__ == '__main__': 185 | unittest.main() 186 | -------------------------------------------------------------------------------- /zabbix_utils/__init__.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | from .api import ZabbixAPI 26 | from .sender import Sender 27 | from .getter import Getter 28 | from .types import ItemValue, APIVersion 29 | from .exceptions import ModuleBaseException, APIRequestError, APINotSupported, ProcessingError 30 | 31 | from .aiosender import AsyncSender 32 | from .aiogetter import AsyncGetter 33 | try: 34 | __import__('aiohttp') 35 | except ModuleNotFoundError: 36 | class AsyncZabbixAPI(): 37 | def __init__(self, *args, **kwargs): 38 | raise ModuleNotFoundError("No module named 'aiohttp'") 39 | else: 40 | from .aioapi import AsyncZabbixAPI 41 | 42 | __all__ = ( 43 | 'ZabbixAPI', 44 | 'AsyncZabbixAPI', 45 | 'APIVersion', 46 | 'Sender', 47 | 'AsyncSender', 48 | 'ItemValue', 49 | 'Getter', 50 | 'AsyncGetter', 51 | 'ModuleBaseException', 52 | 'APIRequestError', 53 | 'APINotSupported', 54 | 'ProcessingError' 55 | ) 56 | -------------------------------------------------------------------------------- /zabbix_utils/aiogetter.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import ssl 26 | import socket 27 | import asyncio 28 | import logging 29 | from typing import Callable, Optional 30 | 31 | from .logger import EmptyHandler 32 | from .types import AgentResponse 33 | from .common import ZabbixProtocol 34 | from .exceptions import ProcessingError 35 | 36 | log = logging.getLogger(__name__) 37 | log.addHandler(EmptyHandler()) 38 | 39 | 40 | class AsyncGetter(): 41 | """Zabbix get asynchronous implementation. 42 | 43 | Args: 44 | host (str, optional): Zabbix agent address. Defaults to `'127.0.0.1'`. 45 | 46 | port (int, optional): Zabbix agent port. Defaults to `10050`. 47 | 48 | timeout (int, optional): Connection timeout value. Defaults to `10`. 49 | 50 | source_ip (str, optional): IP from which to establish connection. Defaults to `None`. 51 | 52 | ssl_context (Callable, optional): Func(), returned prepared ssl.SSLContext. \ 53 | Defaults to `None`. 54 | """ 55 | 56 | def __init__(self, host: str = '127.0.0.1', port: int = 10050, timeout: int = 10, 57 | source_ip: Optional[str] = None, ssl_context: Optional[Callable] = None): 58 | self.host = host 59 | self.port = port 60 | self.timeout = timeout 61 | self.source_ip = source_ip 62 | 63 | self.ssl_context = ssl_context 64 | if self.ssl_context: 65 | if not isinstance(self.ssl_context, Callable): 66 | raise TypeError('Value "ssl_context" should be a function.') 67 | 68 | async def __get_response(self, reader: asyncio.StreamReader) -> Optional[str]: 69 | result = await ZabbixProtocol.parse_async_packet(reader, log, ProcessingError) 70 | 71 | log.debug('Received data: %s', result) 72 | 73 | return result 74 | 75 | async def get(self, key: str) -> Optional[str]: 76 | """Gets item value from Zabbix agent by specified key. 77 | 78 | Args: 79 | key (str): Zabbix item key. 80 | 81 | Returns: 82 | str: Value from Zabbix agent for specified key. 83 | """ 84 | 85 | packet = ZabbixProtocol.create_packet(key, log) 86 | 87 | connection_params = { 88 | "host": self.host, 89 | "port": self.port 90 | } 91 | 92 | if self.source_ip: 93 | connection_params['local_addr'] = (self.source_ip, 0) 94 | 95 | if self.ssl_context: 96 | connection_params['ssl'] = self.ssl_context() 97 | if not isinstance(connection_params['ssl'], ssl.SSLContext): 98 | raise TypeError( 99 | 'Function "ssl_context" must return "ssl.SSLContext".') from None 100 | 101 | connection = asyncio.open_connection(**connection_params) 102 | 103 | try: 104 | reader, writer = await asyncio.wait_for(connection, timeout=self.timeout) 105 | writer.write(packet) 106 | await writer.drain() 107 | except asyncio.TimeoutError as err: 108 | log.error( 109 | 'The connection to %s timed out after %d seconds', 110 | f"{self.host}:{self.port}", 111 | self.timeout 112 | ) 113 | raise err 114 | except (ConnectionRefusedError, socket.gaierror) as err: 115 | log.error( 116 | 'An error occurred while trying to connect to %s: %s', 117 | f"{self.host}:{self.port}", 118 | getattr(err, 'msg', str(err)) 119 | ) 120 | raise err 121 | except (OSError, socket.error) as err: 122 | log.warning( 123 | 'An error occurred while trying to send to %s: %s', 124 | f"{self.host}:{self.port}", 125 | getattr(err, 'msg', str(err)) 126 | ) 127 | raise err 128 | 129 | try: 130 | response = await self.__get_response(reader) 131 | except (ConnectionResetError, asyncio.exceptions.IncompleteReadError) as err: 132 | log.debug('Get value error: %s', err) 133 | log.warning('Check access restrictions in Zabbix agent configuration.') 134 | raise err 135 | log.debug('Response from [%s:%s]: %s', self.host, self.port, response) 136 | 137 | writer.close() 138 | await writer.wait_closed() 139 | 140 | return AgentResponse(response) 141 | -------------------------------------------------------------------------------- /zabbix_utils/exceptions.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | from typing import Union 26 | 27 | from .common import ModuleUtils 28 | 29 | 30 | class ModuleBaseException(Exception): 31 | pass 32 | 33 | 34 | class APIRequestError(ModuleBaseException): 35 | """Exception class when Zabbix API returns error by request. 36 | 37 | Args: 38 | api_error (str|dict): Raw error message from Zabbix API. 39 | """ 40 | def __init__(self, api_error: Union[str, dict]): 41 | if isinstance(api_error, dict): 42 | api_error['body'] = ModuleUtils.hide_private(api_error['body']) 43 | super().__init__("{message} {data}".format(**api_error)) 44 | for key, value in api_error.items(): 45 | setattr(self, key, value) 46 | else: 47 | super().__init__(api_error) 48 | 49 | 50 | class APINotSupported(ModuleBaseException): 51 | """Exception class when object/action is not supported by Zabbix API. 52 | 53 | Args: 54 | message (str): Not supported object/action message. 55 | 56 | version (str): Current version of Zabbix API. 57 | """ 58 | 59 | def __init__(self, message: str, version: str = None): 60 | if version: 61 | message = f"{message} is unsupported for Zabbix {version} version" 62 | super().__init__(message) 63 | 64 | 65 | class ProcessingError(ModuleBaseException): 66 | def __init__(self, *args): 67 | super().__init__(" ".join(map(str, args))) 68 | return 69 | -------------------------------------------------------------------------------- /zabbix_utils/getter.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import socket 26 | import logging 27 | from typing import Callable, Union 28 | 29 | from .logger import EmptyHandler 30 | from .types import AgentResponse 31 | from .common import ZabbixProtocol 32 | from .exceptions import ProcessingError 33 | 34 | log = logging.getLogger(__name__) 35 | log.addHandler(EmptyHandler()) 36 | 37 | 38 | class Getter(): 39 | """Zabbix get synchronous implementation. 40 | 41 | Args: 42 | host (str, optional): Zabbix agent address. Defaults to `'127.0.0.1'`. 43 | 44 | port (int, optional): Zabbix agent port. Defaults to `10050`. 45 | 46 | timeout (int, optional): Connection timeout value. Defaults to `10`. 47 | 48 | use_ipv6 (bool, optional): Specifying IPv6 use instead of IPv4. Defaults to `False`. 49 | 50 | source_ip (str, optional): IP from which to establish connection. Defaults to `None`. 51 | 52 | socket_wrapper (Callable, optional): Func(`conn`) to wrap socket. Defaults to `None`. 53 | """ 54 | 55 | def __init__(self, host: str = '127.0.0.1', port: int = 10050, timeout: int = 10, 56 | use_ipv6: bool = False, source_ip: Union[str, None] = None, 57 | socket_wrapper: Union[Callable, None] = None): 58 | self.host = host 59 | self.port = port 60 | self.timeout = timeout 61 | self.use_ipv6 = use_ipv6 62 | self.source_ip = source_ip 63 | 64 | self.socket_wrapper = socket_wrapper 65 | if self.socket_wrapper: 66 | if not isinstance(self.socket_wrapper, Callable): 67 | raise TypeError('Value "socket_wrapper" should be a function.') 68 | 69 | def __get_response(self, conn: socket) -> Union[str, None]: 70 | result = ZabbixProtocol.parse_sync_packet(conn, log, ProcessingError) 71 | 72 | log.debug('Received data: %s', result) 73 | 74 | return result 75 | 76 | def get(self, key: str) -> Union[str, None]: 77 | """Gets item value from Zabbix agent by specified key. 78 | 79 | Args: 80 | key (str): Zabbix item key. 81 | 82 | Returns: 83 | str: Value from Zabbix agent for specified key. 84 | """ 85 | 86 | packet = ZabbixProtocol.create_packet(key, log) 87 | 88 | try: 89 | if self.use_ipv6: 90 | connection = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 91 | else: 92 | connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 93 | except socket.error: 94 | raise ProcessingError( 95 | f"Error creating socket for {self.host}:{self.port}") from None 96 | 97 | connection.settimeout(self.timeout) 98 | 99 | if self.source_ip: 100 | connection.bind((self.source_ip, 0,)) 101 | 102 | try: 103 | connection.connect((self.host, self.port)) 104 | if self.socket_wrapper is not None: 105 | connection = self.socket_wrapper(connection) 106 | connection.sendall(packet) 107 | except (TimeoutError, socket.timeout) as err: 108 | log.error( 109 | 'The connection to %s timed out after %d seconds', 110 | f"{self.host}:{self.port}", 111 | self.timeout 112 | ) 113 | connection.close() 114 | raise err 115 | except (ConnectionRefusedError, socket.gaierror) as err: 116 | log.error( 117 | 'An error occurred while trying to connect to %s: %s', 118 | f"{self.host}:{self.port}", 119 | getattr(err, 'msg', str(err)) 120 | ) 121 | connection.close() 122 | raise err 123 | except (OSError, socket.error) as err: 124 | log.warning( 125 | 'An error occurred while trying to send to %s: %s', 126 | f"{self.host}:{self.port}", 127 | getattr(err, 'msg', str(err)) 128 | ) 129 | connection.close() 130 | raise err 131 | 132 | try: 133 | response = self.__get_response(connection) 134 | except ConnectionResetError as err: 135 | log.debug('Get value error: %s', err) 136 | log.warning('Check access restrictions in Zabbix agent configuration.') 137 | raise err 138 | log.debug('Response from [%s:%s]: %s', self.host, self.port, response) 139 | 140 | try: 141 | connection.close() 142 | except socket.error: 143 | pass 144 | 145 | return AgentResponse(response) 146 | -------------------------------------------------------------------------------- /zabbix_utils/logger.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | import json 26 | import logging 27 | from .common import ModuleUtils 28 | 29 | 30 | class EmptyHandler(logging.Handler): 31 | """Empty logging handler.""" 32 | 33 | def emit(self, *args, **kwargs): 34 | pass 35 | 36 | 37 | class SensitiveFilter(logging.Filter): 38 | """Filter to hide sensitive Zabbix info (password, auth) in logs""" 39 | 40 | def __init__(self, *args, **kwargs): 41 | super().__init__(*args, **kwargs) 42 | 43 | def __hide_data(self, raw_data): 44 | return json.dumps(ModuleUtils.hide_private(raw_data), indent=4, separators=(',', ': ')) 45 | 46 | def filter(self, record): 47 | if isinstance(record.args, tuple): 48 | record.args = tuple(self.__hide_data(arg) 49 | if isinstance(arg, dict) else arg for arg in record.args) 50 | if isinstance(record.args, dict): 51 | record.args = self.__hide_data(record.args) 52 | 53 | return 1 54 | -------------------------------------------------------------------------------- /zabbix_utils/version.py: -------------------------------------------------------------------------------- 1 | # zabbix_utils 2 | # 3 | # Copyright (C) 2001-2023 Zabbix SIA 4 | # 5 | # Permission is hereby granted, free of charge, to any person 6 | # obtaining a copy of this software and associated documentation 7 | # files (the "Software"), to deal in the Software without restriction, 8 | # including without limitation the rights to use, copy, modify, 9 | # merge, publish, distribute, sublicense, and/or sell copies 10 | # of the Software, and to permit persons to whom the Software 11 | # is furnished to do so, subject to the following conditions: 12 | 13 | # The above copyright notice and this permission notice shall be 14 | # included in all copies or substantial portions of the Software. 15 | 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 18 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 20 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | # OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | __version__ = "2.0.2" 26 | 27 | __min_supported__ = 5.0 28 | __max_supported__ = 7.2 29 | --------------------------------------------------------------------------------