From b79abfedea0ae1014e4036fccbb13be9592a7d5b Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 20 Oct 2023 12:50:11 +0200 Subject: [PATCH 01/23] Create template groups --- config.sample.toml | 1 + zabbix_auto_config/models.py | 1 + zabbix_auto_config/processing.py | 36 ++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/config.sample.toml b/config.sample.toml index 538f5a3..2c419f4 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -55,6 +55,7 @@ managed_inventory = ["location"] #hostgroup_source_prefix = "Source-" #hostgroup_importance_prefix = "Importance-" #extra_siteadmin_hostgroup_prefixes = [] +templategroup_prefix = "Templates-" [source_collectors.mysource] # Name of the source collector module without the .py extension diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 4577a87..13c00c3 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -62,6 +62,7 @@ class ZabbixSettings(ConfigBaseModel): hostgroup_source_prefix: str = "Source-" hostgroup_importance_prefix: str = "Importance-" + templategroup_prefix: str = "Templates-" # Prefixes for extra host groups to create based on the host groups # in the siteadmin mapping. diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index d5f46be..f524e73 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1094,6 +1094,39 @@ def create_extra_hostgroups( continue self.create_hostgroup(hostgroup) + def create_templategroup(self, templategroup_name: str) -> Optional[str]: + if not self.config.dryrun: + logging.debug("Creating template group: '%s'", templategroup_name) + try: + result = self.api.templategroup.create(name=templategroup_name) + return result["groupids"][0] + except pyzabbix.ZabbixAPIException as e: + logging.error( + "Error when creating template group '%s': %s", + templategroup_name, + e.args, + ) + return None + else: + logging.debug("DRYRUN: Creating template group: '%s'", templategroup_name) + return "-1" + + def create_templategroups(self) -> None: + """>=6.4. ONLY: Creates template groups for each host group in + the mapping file.""" + tgroups = self.api.templategroup.get(output=["name", "groupid"]) + templategroup_names = [h["name"] for h in tgroups] + + mapping = utils.mapping_values_with_prefix( + self.siteadmin_hostgroup_map, # this is copied in the function + prefix=self.config.templategroup_prefix, + ) + for templategroups in mapping.values(): + for templategroup in templategroups: + if templategroup in templategroup_names: + continue + self.create_templategroup(templategroup) + def do_update(self): managed_hostgroup_names = set( itertools.chain.from_iterable(self.property_hostgroup_map.values()) @@ -1107,6 +1140,9 @@ def do_update(self): # Create extra host groups if necessary if self.config.extra_siteadmin_hostgroup_prefixes: self.create_extra_hostgroups(existing_hostgroups) + + # Create template groups if necessary + self.create_templategroups() zabbix_hostgroups = {} for zabbix_hostgroup in existing_hostgroups: From 561d5e618bb98629c5a3bba5c48841adc3bbf8eb Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 27 Oct 2023 11:35:26 +0200 Subject: [PATCH 02/23] Check version before creating template groups --- README.md | 4 +- config.sample.toml | 2 +- zabbix_auto_config/models.py | 26 +++++++++++-- zabbix_auto_config/processing.py | 67 ++++++++++++++++++-------------- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 78ab8bd..af83578 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # About -Zabbix-auto-config is an utility that aims to automatically configure hosts, host groups, host inventories and templates in the monitoring software [Zabbix](https://www.zabbix.com/). +Zabbix-auto-config is an utility that aims to automatically configure hosts, host groups, host inventories, template groups and templates in the monitoring software [Zabbix](https://www.zabbix.com/). Note: This is only tested with Zabbix 5.0 LTS. @@ -101,7 +101,7 @@ def collect(*args: Any, **kwargs: Any) -> List[Host]: if __name__ == "__main__": for host in collect(): - print(host.json()) + print(host.model_dump_json()) EOF cat > path/to/host_modifier_dir/mod.py << EOF from zabbix_auto_config.models import Host diff --git a/config.sample.toml b/config.sample.toml index 2c419f4..99fc4bc 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -55,7 +55,7 @@ managed_inventory = ["location"] #hostgroup_source_prefix = "Source-" #hostgroup_importance_prefix = "Importance-" #extra_siteadmin_hostgroup_prefixes = [] -templategroup_prefix = "Templates-" +#templategroup_prefix = "Templates-" [source_collectors.mysource] # Name of the source collector module without the .py extension diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 13c00c3..bab192a 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -1,6 +1,8 @@ +from __future__ import annotations + import logging from pathlib import Path -from typing import Any, Dict, List, Optional, Set, Tuple, Union +from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple, Union from pydantic import BaseModel, ValidationInfo from pydantic import BaseModel as PydanticBaseModel @@ -51,6 +53,7 @@ class ZabbixSettings(ConfigBaseModel): description="The timeout in seconds for HTTP requests to Zabbix.", ge=0, ) + create_templategroups: bool = True tags_prefix: str = "zac_" managed_inventory: List[str] = [] @@ -66,10 +69,10 @@ class ZabbixSettings(ConfigBaseModel): # Prefixes for extra host groups to create based on the host groups # in the siteadmin mapping. - # e.g. Siteadmin-foo -> Templates-foo if list is ["Templates-"] + # e.g. Siteadmin-foo -> Secondary-foo if list is ["Secondary-"] # The groups must have prefixes separated by a hyphen (-) in order # to replace them with any of these prefixes. - # These groups are not managed by ZAC beyond creating them. + # These groups are not managed by ZAC beyond their creation. extra_siteadmin_hostgroup_prefixes: Set[str] = set() @field_validator("timeout") @@ -280,6 +283,23 @@ def merge(self, other: "Host") -> None: self.proxy_pattern = proxy_patterns.pop() +class ZabbixVersion(NamedTuple): + major: int + minor: int + patch: int + + @classmethod + def from_version_string(cls, version_string: str) -> ZabbixVersion: + parts = version_string.split(".") + if len(parts) != 3: + raise ValueError(f"Cannot parse Zabbix version string: {version_string}") + return cls( + major=int(parts[0]), + minor=int(parts[1]), + patch=int(parts[2]), + ) + + class HostActions(BaseModel): add: List[str] = [] remove: List[str] = [] diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index f524e73..bfcdbe4 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -604,6 +604,9 @@ def __init__(self, name, state, db_uri, settings: models.Settings): os.path.join(self.config.map_dir, "siteadmin_hostgroup_map.txt") ) + ver = self.api.apiinfo.version() + self.zabbix_version = models.ZabbixVersion.from_version_string(ver) + def work(self): start_time = time.time() logging.info("Zabbix update starting") @@ -983,8 +986,8 @@ def clear_templates(self, templates, host): logging.debug("DRYRUN: Clearing templates on host: '%s'", host["host"]) def set_templates(self, templates, host): - logging.debug("Setting templates on host: '%s'", host["host"]) if not self.config.dryrun: + logging.debug("Setting templates on host: '%s'", host["host"]) try: templates = [{"templateid": template_id} for _, template_id in templates.items()] self.api.host.update(hostid=host["hostid"], templates=templates) @@ -1064,24 +1067,27 @@ def set_hostgroups(self, hostgroups, host): else: logging.debug("DRYRUN: Setting hostgroups on host: '%s'", host["host"]) - def create_hostgroup(self, hostgroup_name): - if not self.config.dryrun: - logging.debug("Creating hostgroup: '%s'", hostgroup_name) - try: - result = self.api.hostgroup.create(name=hostgroup_name) - return result["groupids"][0] - except pyzabbix.ZabbixAPIException as e: - logging.error("Error when creating hostgroups '%s': %s", hostgroup_name, e.args) - else: + def create_hostgroup(self, hostgroup_name: str) -> Optional[str]: + if self.config.dryrun: logging.debug("DRYRUN: Creating hostgroup: '%s'", hostgroup_name) - return "-1" + return None + + logging.debug("Creating hostgroup: '%s'", hostgroup_name) + try: + result = self.api.hostgroup.create(name=hostgroup_name) + return result["groupids"][0] + except pyzabbix.ZabbixAPIException as e: + logging.error( + "Error when creating hostgroups '%s': %s", hostgroup_name, e.args + ) + return None def create_extra_hostgroups( self, existing_hostgroups: List[Dict[str, str]] ) -> None: """Creates additonal host groups based on the prefixes specified in the config file. These host groups are not assigned hosts by ZAC.""" - hostgroup_names = [h["name"] for h in existing_hostgroups] + hostgroup_names = set(h["name"] for h in existing_hostgroups) for prefix in self.config.extra_siteadmin_hostgroup_prefixes: mapping = utils.mapping_values_with_prefix( @@ -1095,30 +1101,33 @@ def create_extra_hostgroups( self.create_hostgroup(hostgroup) def create_templategroup(self, templategroup_name: str) -> Optional[str]: - if not self.config.dryrun: - logging.debug("Creating template group: '%s'", templategroup_name) - try: - result = self.api.templategroup.create(name=templategroup_name) - return result["groupids"][0] - except pyzabbix.ZabbixAPIException as e: - logging.error( - "Error when creating template group '%s': %s", - templategroup_name, - e.args, - ) - return None - else: + if self.config.dryrun: logging.debug("DRYRUN: Creating template group: '%s'", templategroup_name) - return "-1" + return None + + logging.debug("Creating template group: '%s'", templategroup_name) + try: + result = self.api.templategroup.create(name=templategroup_name) + return result["groupids"][0] + except pyzabbix.ZabbixAPIException as e: + logging.error( + "Error when creating template group '%s': %s", + templategroup_name, + e.args, + ) + return None def create_templategroups(self) -> None: - """>=6.4. ONLY: Creates template groups for each host group in + """>=6.4 ONLY: Creates template groups for each host group in the mapping file.""" + if self.zabbix_version < (6, 4, 0) or not self.config.create_templategroups: + return + tgroups = self.api.templategroup.get(output=["name", "groupid"]) - templategroup_names = [h["name"] for h in tgroups] + templategroup_names = set(h["name"] for h in tgroups) mapping = utils.mapping_values_with_prefix( - self.siteadmin_hostgroup_map, # this is copied in the function + self.siteadmin_hostgroup_map, prefix=self.config.templategroup_prefix, ) for templategroups in mapping.values(): From 075d31d2b6617442f0401646253cd4d294c29632 Mon Sep 17 00:00:00 2001 From: pederhan Date: Mon, 30 Oct 2023 10:57:17 +0100 Subject: [PATCH 03/23] Add ZabbixVersion.from_version_string docstring --- zabbix_auto_config/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index bab192a..09831e9 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -290,6 +290,10 @@ class ZabbixVersion(NamedTuple): @classmethod def from_version_string(cls, version_string: str) -> ZabbixVersion: + """Constructs a ZabbixVersion from a semantic version string from the API. + + See: + """ parts = version_string.split(".") if len(parts) != 3: raise ValueError(f"Cannot parse Zabbix version string: {version_string}") From 89d90f4a7be7d27e7a565d87c180e4e669b6467a Mon Sep 17 00:00:00 2001 From: pederhan Date: Mon, 30 Oct 2023 11:06:18 +0100 Subject: [PATCH 04/23] Reorder [zabbix] config options, uncomment defaults --- config.sample.toml | 20 ++++++++++++-------- zabbix_auto_config/models.py | 2 ++ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/config.sample.toml b/config.sample.toml index 99fc4bc..c0d31db 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -49,13 +49,17 @@ tags_prefix = "zac_" managed_inventory = ["location"] # Names of hostgroups that zabbix-auto-config will manage. -#hostgroup_all = "All-hosts" -#hostgroup_manual = "All-manual-hosts" -#hostgroup_disabled = "All-auto-disabled-hosts" -#hostgroup_source_prefix = "Source-" -#hostgroup_importance_prefix = "Importance-" -#extra_siteadmin_hostgroup_prefixes = [] -#templategroup_prefix = "Templates-" +hostgroup_all = "All-hosts" +hostgroup_manual = "All-manual-hosts" +hostgroup_disabled = "All-auto-disabled-hosts" +hostgroup_source_prefix = "Source-" +hostgroup_importance_prefix = "Importance-" + +# Template group creation (Zabbix >=6.4 only) +create_templategroups = true +templategroup_prefix = "Templates-" + +extra_siteadmin_hostgroup_prefixes = [] [source_collectors.mysource] # Name of the source collector module without the .py extension @@ -88,5 +92,5 @@ another_kwarg = "value2" # We can pass an arbitrary number of kwargs to module_name = "mysource" update_interval = 60 error_tolerance = 0 # no tolerance for errors (default) -exit_on_error = true # exit application if source fails +exit_on_error = true # exit application if source fails (default) source = "other" # extra kwarg used in mysource module diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 09831e9..1322bb0 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -65,6 +65,8 @@ class ZabbixSettings(ConfigBaseModel): hostgroup_source_prefix: str = "Source-" hostgroup_importance_prefix: str = "Importance-" + + create_templategroups: bool = True templategroup_prefix: str = "Templates-" # Prefixes for extra host groups to create based on the host groups From 107359ab0d4738b2f9d3b360de304e8e05413a10 Mon Sep 17 00:00:00 2001 From: pederhan Date: Mon, 30 Oct 2023 14:22:33 +0100 Subject: [PATCH 05/23] Add ZabbixVersion tests --- tests/test_models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_models.py b/tests/test_models.py index 7e0c8eb..9b287dc 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -2,6 +2,7 @@ from typing import Optional import pytest from pydantic import ValidationError + from zabbix_auto_config import models From 6458ddcb9b197e10ace08741b66fd356273fcd2c Mon Sep 17 00:00:00 2001 From: pederhan Date: Mon, 30 Oct 2023 14:32:14 +0100 Subject: [PATCH 06/23] Log reason for skipping template group creation --- zabbix_auto_config/processing.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index bfcdbe4..24cbb54 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1120,7 +1120,11 @@ def create_templategroup(self, templategroup_name: str) -> Optional[str]: def create_templategroups(self) -> None: """>=6.4 ONLY: Creates template groups for each host group in the mapping file.""" - if self.zabbix_version < (6, 4, 0) or not self.config.create_templategroups: + if not self.config.create_templategroups: + logger.debug("Skipping template group creation. Feature is disabled.") + return + elif self.zabbix_version < (6, 4, 0): + logger.info("Skipping template group creation. Feature requires Zabbix >= 6.4.0.") return tgroups = self.api.templategroup.get(output=["name", "groupid"]) From 4c6bb12a9bbe459e51e4dc61a589904aed40a90b Mon Sep 17 00:00:00 2001 From: pederhan Date: Wed, 1 Nov 2023 13:02:39 +0100 Subject: [PATCH 07/23] Log group creation --- zabbix_auto_config/processing.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 24cbb54..18c4230 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1075,7 +1075,9 @@ def create_hostgroup(self, hostgroup_name: str) -> Optional[str]: logging.debug("Creating hostgroup: '%s'", hostgroup_name) try: result = self.api.hostgroup.create(name=hostgroup_name) - return result["groupids"][0] + groupid = result["groupids"][0] + logger.info("Created host group '%s' (%s)", hostgroup_name, groupid) + return groupid except pyzabbix.ZabbixAPIException as e: logging.error( "Error when creating hostgroups '%s': %s", hostgroup_name, e.args @@ -1108,7 +1110,9 @@ def create_templategroup(self, templategroup_name: str) -> Optional[str]: logging.debug("Creating template group: '%s'", templategroup_name) try: result = self.api.templategroup.create(name=templategroup_name) - return result["groupids"][0] + groupid = result["groupids"][0] + logger.info("Created template group '%s' (%s)", templategroup_name, groupid) + return groupid except pyzabbix.ZabbixAPIException as e: logging.error( "Error when creating template group '%s': %s", From b12a7f7f852fab16357b307691c3a6afc261b890 Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 10:44:55 +0100 Subject: [PATCH 08/23] Change incorrect logging calls --- zabbix_auto_config/processing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 18c4230..8955937 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1076,7 +1076,7 @@ def create_hostgroup(self, hostgroup_name: str) -> Optional[str]: try: result = self.api.hostgroup.create(name=hostgroup_name) groupid = result["groupids"][0] - logger.info("Created host group '%s' (%s)", hostgroup_name, groupid) + logging.info("Created host group '%s' (%s)", hostgroup_name, groupid) return groupid except pyzabbix.ZabbixAPIException as e: logging.error( @@ -1111,7 +1111,7 @@ def create_templategroup(self, templategroup_name: str) -> Optional[str]: try: result = self.api.templategroup.create(name=templategroup_name) groupid = result["groupids"][0] - logger.info("Created template group '%s' (%s)", templategroup_name, groupid) + logging.info("Created template group '%s' (%s)", templategroup_name, groupid) return groupid except pyzabbix.ZabbixAPIException as e: logging.error( @@ -1125,10 +1125,10 @@ def create_templategroups(self) -> None: """>=6.4 ONLY: Creates template groups for each host group in the mapping file.""" if not self.config.create_templategroups: - logger.debug("Skipping template group creation. Feature is disabled.") + logging.debug("Skipping template group creation. Feature is disabled.") return elif self.zabbix_version < (6, 4, 0): - logger.info("Skipping template group creation. Feature requires Zabbix >= 6.4.0.") + logging.info("Skipping template group creation. Feature requires Zabbix >= 6.4.0.") return tgroups = self.api.templategroup.get(output=["name", "groupid"]) From 0192c9ebe517e3d90081622944f81431fa0fc32e Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 12:54:57 +0100 Subject: [PATCH 09/23] Disable template group creation by default --- zabbix_auto_config/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 1322bb0..36ebd3f 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -66,7 +66,7 @@ class ZabbixSettings(ConfigBaseModel): hostgroup_source_prefix: str = "Source-" hostgroup_importance_prefix: str = "Importance-" - create_templategroups: bool = True + create_templategroups: bool = False templategroup_prefix: str = "Templates-" # Prefixes for extra host groups to create based on the host groups From 1aff43935786c558480dba6151d4bc1b3fceac6a Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 12:57:05 +0100 Subject: [PATCH 10/23] Add Zabbix <6.4 compatibility --- zabbix_auto_config/models.py | 3 ++ zabbix_auto_config/processing.py | 61 ++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 22 deletions(-) diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 36ebd3f..1cd2bf8 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -290,6 +290,9 @@ class ZabbixVersion(NamedTuple): minor: int patch: int + def __str__(self) -> str: + return f"{self.major}.{self.minor}.{self.patch}" + @classmethod def from_version_string(cls, version_string: str) -> ZabbixVersion: """Constructs a ZabbixVersion from a semantic version string from the API. diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 8955937..579b3d4 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1121,28 +1121,44 @@ def create_templategroup(self, templategroup_name: str) -> Optional[str]: ) return None - def create_templategroups(self) -> None: - """>=6.4 ONLY: Creates template groups for each host group in - the mapping file.""" - if not self.config.create_templategroups: - logging.debug("Skipping template group creation. Feature is disabled.") - return - elif self.zabbix_version < (6, 4, 0): - logging.info("Skipping template group creation. Feature requires Zabbix >= 6.4.0.") - return + def create_templategroups(self, managed_hostgroup_names: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None: + """Creates template groups for each managed host group. + + For Zabbix <6.4, host groups with the configured template group prefix + are created instead.""" + tgroups = set(utils.with_prefix(hg, self.config.templategroup_prefix) for hg in managed_hostgroup_names) + if self.zabbix_version >= (6, 4, 0): + logging.debug("Zabbix version is %s. Creating template groups.", self.zabbix_version) + self._create_templategroups(tgroups) + else: + logging.debug("Zabbix version is %s. Creating template groups as host groups.", self.zabbix_version) + self._create_templategroups_pre_64_compat(tgroups, existing_hostgroups) - tgroups = self.api.templategroup.get(output=["name", "groupid"]) - templategroup_names = set(h["name"] for h in tgroups) + + def _create_templategroups(self, tgroups: Set[str]) -> None: + """Zabbix >=6.4 template group creation method.""" + res = self.api.templategroup.get(output=["name", "groupid"]) + existing_tgroups = set(tg["name"] for tg in res) + + # for templategroups in mapping.values(): + for tgroup in tgroups: + if tgroup in existing_tgroups: + continue + self.create_templategroup(tgroup) - mapping = utils.mapping_values_with_prefix( - self.siteadmin_hostgroup_map, - prefix=self.config.templategroup_prefix, - ) - for templategroups in mapping.values(): - for templategroup in templategroups: - if templategroup in templategroup_names: - continue - self.create_templategroup(templategroup) + def _create_templategroups_pre_64_compat(self, tgroups: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None: + """Zabbix <6.4 template group compatibility fallback method. + Template groups don't exist in Zabbix <6.4, so we create host + groups to fulfill the same purpose. + + Creates template host groups for all groups in the siteadmin + mapping file with the configured template group prefix.""" + existing_hgroup_names = set(h["name"] for h in existing_hostgroups) + + for tgroup in tgroups: + if tgroup in existing_hgroup_names: + continue + self.create_hostgroup(tgroup) def do_update(self): managed_hostgroup_names = set( @@ -1158,8 +1174,9 @@ def do_update(self): if self.config.extra_siteadmin_hostgroup_prefixes: self.create_extra_hostgroups(existing_hostgroups) - # Create template groups if necessary - self.create_templategroups() + # Create template groups if enabled + if self.config.create_templategroups: + self.create_templategroups(managed_hostgroup_names, existing_hostgroups) zabbix_hostgroups = {} for zabbix_hostgroup in existing_hostgroups: From c1295714cd188d8ab255127af4a000934d2a7ccc Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 13:08:55 +0100 Subject: [PATCH 11/23] Make `create_extra_hostgroups` consistent w/ rest --- zabbix_auto_config/processing.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 579b3d4..0c5c187 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1085,22 +1085,18 @@ def create_hostgroup(self, hostgroup_name: str) -> Optional[str]: return None def create_extra_hostgroups( - self, existing_hostgroups: List[Dict[str, str]] + self, managed_hostgroup_names: Set[str], existing_hostgroups: List[Dict[str, str]] ) -> None: """Creates additonal host groups based on the prefixes specified in the config file. These host groups are not assigned hosts by ZAC.""" hostgroup_names = set(h["name"] for h in existing_hostgroups) for prefix in self.config.extra_siteadmin_hostgroup_prefixes: - mapping = utils.mapping_values_with_prefix( - self.siteadmin_hostgroup_map, # this is copied in the function - prefix=prefix, - ) - for hostgroups in mapping.values(): - for hostgroup in hostgroups: - if hostgroup in hostgroup_names: - continue - self.create_hostgroup(hostgroup) + hgroups = [utils.with_prefix(hg, prefix) for hg in managed_hostgroup_names] + for hostgroup in hgroups: + if hostgroup in hostgroup_names: + continue + self.create_hostgroup(hostgroup) def create_templategroup(self, templategroup_name: str) -> Optional[str]: if self.config.dryrun: @@ -1172,7 +1168,7 @@ def do_update(self): # Create extra host groups if necessary if self.config.extra_siteadmin_hostgroup_prefixes: - self.create_extra_hostgroups(existing_hostgroups) + self.create_extra_hostgroups(managed_hostgroup_names, existing_hostgroups) # Create template groups if enabled if self.config.create_templategroups: From 2e8c152e33898260f2c71e699910491ef34f09e4 Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 13:10:45 +0100 Subject: [PATCH 12/23] Add missing typing import --- zabbix_auto_config/processing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 0c5c187..fd4c192 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -13,7 +13,7 @@ import signal import itertools import queue -from typing import Dict, List, TYPE_CHECKING, Optional +from typing import Dict, List, TYPE_CHECKING, Optional, Set import psycopg2 from pydantic import ValidationError From 4059bfee872da1531af5323be11063310b515ad6 Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 13:15:30 +0100 Subject: [PATCH 13/23] Revert "Make `create_extra_hostgroups` consistent w/ rest" This reverts commit 5f26e198115b92631d2be1f4932ebe699b3f7f09. --- zabbix_auto_config/processing.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index fd4c192..7c229c7 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1085,18 +1085,22 @@ def create_hostgroup(self, hostgroup_name: str) -> Optional[str]: return None def create_extra_hostgroups( - self, managed_hostgroup_names: Set[str], existing_hostgroups: List[Dict[str, str]] + self, existing_hostgroups: List[Dict[str, str]] ) -> None: """Creates additonal host groups based on the prefixes specified in the config file. These host groups are not assigned hosts by ZAC.""" hostgroup_names = set(h["name"] for h in existing_hostgroups) for prefix in self.config.extra_siteadmin_hostgroup_prefixes: - hgroups = [utils.with_prefix(hg, prefix) for hg in managed_hostgroup_names] - for hostgroup in hgroups: - if hostgroup in hostgroup_names: - continue - self.create_hostgroup(hostgroup) + mapping = utils.mapping_values_with_prefix( + self.siteadmin_hostgroup_map, # this is copied in the function + prefix=prefix, + ) + for hostgroups in mapping.values(): + for hostgroup in hostgroups: + if hostgroup in hostgroup_names: + continue + self.create_hostgroup(hostgroup) def create_templategroup(self, templategroup_name: str) -> Optional[str]: if self.config.dryrun: @@ -1168,7 +1172,7 @@ def do_update(self): # Create extra host groups if necessary if self.config.extra_siteadmin_hostgroup_prefixes: - self.create_extra_hostgroups(managed_hostgroup_names, existing_hostgroups) + self.create_extra_hostgroups(existing_hostgroups) # Create template groups if enabled if self.config.create_templategroups: From 45dae1e485c74180deff99a47cd6ee461b634583 Mon Sep 17 00:00:00 2001 From: pederhan Date: Fri, 3 Nov 2023 16:47:13 +0100 Subject: [PATCH 14/23] Only create template groups for siteadmin groups --- config.sample.toml | 3 ++- zabbix_auto_config/processing.py | 21 ++++++++++++++------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/config.sample.toml b/config.sample.toml index c0d31db..aa18081 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -55,7 +55,8 @@ hostgroup_disabled = "All-auto-disabled-hosts" hostgroup_source_prefix = "Source-" hostgroup_importance_prefix = "Importance-" -# Template group creation (Zabbix >=6.4 only) +# Template group creation +# NOTE: will create host groups if enabled on Zabbix <6.4 create_templategroups = true templategroup_prefix = "Templates-" diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 7c229c7..275760a 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1121,12 +1121,19 @@ def create_templategroup(self, templategroup_name: str) -> Optional[str]: ) return None - def create_templategroups(self, managed_hostgroup_names: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None: - """Creates template groups for each managed host group. - - For Zabbix <6.4, host groups with the configured template group prefix - are created instead.""" - tgroups = set(utils.with_prefix(hg, self.config.templategroup_prefix) for hg in managed_hostgroup_names) + def create_templategroups(self, existing_hostgroups: List[Dict[str, str]]) -> None: + """Creates template groups for each host group in the siteadmin + mapping file with the configured template group prefix. + + For Zabbix <6.4, host groups are created instead of template groups.""" + # Construct a set of all template group names from siteadmin mapping file + # by replacing the host group prefix with the template group prefix + tgroups = set( + utils.with_prefix(tg, self.config.templategroup_prefix) + for tg in itertools.chain.from_iterable( + self.siteadmin_hostgroup_map.values() + ) + ) if self.zabbix_version >= (6, 4, 0): logging.debug("Zabbix version is %s. Creating template groups.", self.zabbix_version) self._create_templategroups(tgroups) @@ -1176,7 +1183,7 @@ def do_update(self): # Create template groups if enabled if self.config.create_templategroups: - self.create_templategroups(managed_hostgroup_names, existing_hostgroups) + self.create_templategroups(existing_hostgroups) zabbix_hostgroups = {} for zabbix_hostgroup in existing_hostgroups: From 2e2c887b978a2c8efd564625d35958bc60627c00 Mon Sep 17 00:00:00 2001 From: pederhan Date: Thu, 9 Nov 2023 10:44:23 +0100 Subject: [PATCH 15/23] Remove comment, whitespace --- zabbix_auto_config/processing.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 275760a..da2b97d 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1146,8 +1146,6 @@ def _create_templategroups(self, tgroups: Set[str]) -> None: """Zabbix >=6.4 template group creation method.""" res = self.api.templategroup.get(output=["name", "groupid"]) existing_tgroups = set(tg["name"] for tg in res) - - # for templategroups in mapping.values(): for tgroup in tgroups: if tgroup in existing_tgroups: continue @@ -1161,7 +1159,6 @@ def _create_templategroups_pre_64_compat(self, tgroups: Set[str], existing_hostg Creates template host groups for all groups in the siteadmin mapping file with the configured template group prefix.""" existing_hgroup_names = set(h["name"] for h in existing_hostgroups) - for tgroup in tgroups: if tgroup in existing_hgroup_names: continue From 35e3c07be94f708975474ff15651e829899dada6 Mon Sep 17 00:00:00 2001 From: pederhan Date: Tue, 14 Nov 2023 09:24:43 +0100 Subject: [PATCH 16/23] Use packaging.version, lower requirement to 6.2 --- pyproject.toml | 1 + zabbix_auto_config/models.py | 26 +------------------------- zabbix_auto_config/processing.py | 17 +++++++++-------- 3 files changed, 11 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fd46e51..9db9b9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ dependencies = [ "pyzabbix>=1.3.0", "requests>=1.0.0", "tomli>=2.0.0", + "packaging>=24.0", ] [project.optional-dependencies] diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 1cd2bf8..3b319f7 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -2,7 +2,7 @@ import logging from pathlib import Path -from typing import Any, Dict, List, NamedTuple, Optional, Set, Tuple, Union +from typing import Any, Dict, List, Optional, Set, Tuple, Union from pydantic import BaseModel, ValidationInfo from pydantic import BaseModel as PydanticBaseModel @@ -285,30 +285,6 @@ def merge(self, other: "Host") -> None: self.proxy_pattern = proxy_patterns.pop() -class ZabbixVersion(NamedTuple): - major: int - minor: int - patch: int - - def __str__(self) -> str: - return f"{self.major}.{self.minor}.{self.patch}" - - @classmethod - def from_version_string(cls, version_string: str) -> ZabbixVersion: - """Constructs a ZabbixVersion from a semantic version string from the API. - - See: - """ - parts = version_string.split(".") - if len(parts) != 3: - raise ValueError(f"Cannot parse Zabbix version string: {version_string}") - return cls( - major=int(parts[0]), - minor=int(parts[1]), - patch=int(parts[2]), - ) - - class HostActions(BaseModel): add: List[str] = [] remove: List[str] = [] diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index da2b97d..5ce89c9 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -15,6 +15,7 @@ import queue from typing import Dict, List, TYPE_CHECKING, Optional, Set +from packaging.version import Version import psycopg2 from pydantic import ValidationError import pyzabbix @@ -605,7 +606,7 @@ def __init__(self, name, state, db_uri, settings: models.Settings): ) ver = self.api.apiinfo.version() - self.zabbix_version = models.ZabbixVersion.from_version_string(ver) + self.zabbix_version = Version(ver) def work(self): start_time = time.time() @@ -1125,7 +1126,7 @@ def create_templategroups(self, existing_hostgroups: List[Dict[str, str]]) -> No """Creates template groups for each host group in the siteadmin mapping file with the configured template group prefix. - For Zabbix <6.4, host groups are created instead of template groups.""" + For Zabbix <6.2, host groups are created instead of template groups.""" # Construct a set of all template group names from siteadmin mapping file # by replacing the host group prefix with the template group prefix tgroups = set( @@ -1134,16 +1135,16 @@ def create_templategroups(self, existing_hostgroups: List[Dict[str, str]]) -> No self.siteadmin_hostgroup_map.values() ) ) - if self.zabbix_version >= (6, 4, 0): + if self.zabbix_version.release >= (6, 2, 0): logging.debug("Zabbix version is %s. Creating template groups.", self.zabbix_version) self._create_templategroups(tgroups) else: logging.debug("Zabbix version is %s. Creating template groups as host groups.", self.zabbix_version) - self._create_templategroups_pre_64_compat(tgroups, existing_hostgroups) + self._create_templategroups_pre_62_compat(tgroups, existing_hostgroups) def _create_templategroups(self, tgroups: Set[str]) -> None: - """Zabbix >=6.4 template group creation method.""" + """Zabbix >=6.2 template group creation method.""" res = self.api.templategroup.get(output=["name", "groupid"]) existing_tgroups = set(tg["name"] for tg in res) for tgroup in tgroups: @@ -1151,9 +1152,9 @@ def _create_templategroups(self, tgroups: Set[str]) -> None: continue self.create_templategroup(tgroup) - def _create_templategroups_pre_64_compat(self, tgroups: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None: - """Zabbix <6.4 template group compatibility fallback method. - Template groups don't exist in Zabbix <6.4, so we create host + def _create_templategroups_pre_62_compat(self, tgroups: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None: + """Zabbix <6.2 template group compatibility fallback method. + Template groups don't exist in Zabbix <6.2, so we create host groups to fulfill the same purpose. Creates template host groups for all groups in the siteadmin From b9ddb2f104199d36592db86bfff50968dca6b7c8 Mon Sep 17 00:00:00 2001 From: pederhan Date: Tue, 14 Nov 2023 10:53:16 +0100 Subject: [PATCH 17/23] Add Zabbix API version parsing tests --- tests/test_version.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_version.py diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..ce71eb5 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,40 @@ +"""Santiy testing of Zabbix API version parsing. + +Tests against known versions of Zabbix. Expects support for alpha, beta and rc. +""" + +from typing import Tuple +from packaging.version import Version +import pytest + + +@pytest.mark.parametrize( + "version, release", + [ + # Certain major versions released in 2023 + ("7.0.0", (7, 0, 0)), + ("6.4.8", (6, 4, 8)), + ("6.0.23", (6, 0, 23)), + ("5.0.39", (5, 0, 39)), + ("6.2.9", (6, 2, 9)), + # Pre-release versions + ("7.0.0alpha7", (7, 0, 0)), + ("7.0.0a7", (7, 0, 0)), # short form + ("6.4.0beta6", (6, 4, 0)), + ("6.4.0b6", (6, 4, 0)), # short form + ("6.4.8rc1", (6, 4, 8)), + ], +) +def test_version(version: str, release: Tuple[int, int, int]): + """Test that the version string is parsed correctly.""" + v = Version(version) + assert v.release == release + assert v.major == release[0] + assert v.minor == release[1] + assert v.micro == release[2] + + # Test comparison + assert v.release < (999, 999, 999) + assert v.release > (0, 0, 0) + assert v > Version("0.0.0") + assert v < Version("999.999.999") From 17c7aaece76b6e82d3119393b9d34ace540af4f3 Mon Sep 17 00:00:00 2001 From: pederhan Date: Tue, 14 Nov 2023 11:02:05 +0100 Subject: [PATCH 18/23] Remove unused imports --- tests/test_models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_models.py b/tests/test_models.py index 9b287dc..7e0c8eb 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -2,7 +2,6 @@ from typing import Optional import pytest from pydantic import ValidationError - from zabbix_auto_config import models From ca146b181856ead3175710cbb4cbbc6787fd3625 Mon Sep 17 00:00:00 2001 From: pederhan Date: Tue, 14 Nov 2023 11:14:38 +0100 Subject: [PATCH 19/23] Update sample config comment --- config.sample.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.sample.toml b/config.sample.toml index aa18081..aa69f42 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -56,7 +56,7 @@ hostgroup_source_prefix = "Source-" hostgroup_importance_prefix = "Importance-" # Template group creation -# NOTE: will create host groups if enabled on Zabbix <6.4 +# NOTE: will create host groups if enabled on Zabbix <6.2 create_templategroups = true templategroup_prefix = "Templates-" From aca12984d0b329eddf0edc5d5c433b3b71a811f2 Mon Sep 17 00:00:00 2001 From: pederhan Date: Wed, 15 Nov 2023 10:01:15 +0100 Subject: [PATCH 20/23] Remove duplicate definition --- zabbix_auto_config/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zabbix_auto_config/models.py b/zabbix_auto_config/models.py index 3b319f7..687011c 100644 --- a/zabbix_auto_config/models.py +++ b/zabbix_auto_config/models.py @@ -53,7 +53,6 @@ class ZabbixSettings(ConfigBaseModel): description="The timeout in seconds for HTTP requests to Zabbix.", ge=0, ) - create_templategroups: bool = True tags_prefix: str = "zac_" managed_inventory: List[str] = [] From 87d6f1f63460f4808a923cdd3bb68c913b267c30 Mon Sep 17 00:00:00 2001 From: pederhan Date: Wed, 22 Nov 2023 12:51:41 +0100 Subject: [PATCH 21/23] Add packaging dependency to pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9db9b9f..9879390 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "pyzabbix>=1.3.0", "requests>=1.0.0", "tomli>=2.0.0", - "packaging>=24.0", + "packaging>=23.2", ] [project.optional-dependencies] From 5bc97b9e595c1a86c62655fc2981065c5a05db95 Mon Sep 17 00:00:00 2001 From: pederhan Date: Tue, 19 Mar 2024 13:53:18 +0100 Subject: [PATCH 22/23] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af83578..d839266 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Zabbix-auto-config is an utility that aims to automatically configure hosts, host groups, host inventories, template groups and templates in the monitoring software [Zabbix](https://www.zabbix.com/). -Note: This is only tested with Zabbix 5.0 LTS. +Note: Only tested with Zabbix 6.0 and 6.4. ## Requirements From 9f1840e1709c6603b3907c9e2e7c973efc19c97c Mon Sep 17 00:00:00 2001 From: pederhan Date: Wed, 20 Mar 2024 11:20:37 +0100 Subject: [PATCH 23/23] Update `_create_templategroups*` method docstrings --- zabbix_auto_config/processing.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/zabbix_auto_config/processing.py b/zabbix_auto_config/processing.py index 5ce89c9..17abe46 100644 --- a/zabbix_auto_config/processing.py +++ b/zabbix_auto_config/processing.py @@ -1144,7 +1144,11 @@ def create_templategroups(self, existing_hostgroups: List[Dict[str, str]]) -> No def _create_templategroups(self, tgroups: Set[str]) -> None: - """Zabbix >=6.2 template group creation method.""" + """Create the given template groups if they don't exist. + + Args: + tgroups: A set of template group names to create. + """ res = self.api.templategroup.get(output=["name", "groupid"]) existing_tgroups = set(tg["name"] for tg in res) for tgroup in tgroups: @@ -1153,12 +1157,14 @@ def _create_templategroups(self, tgroups: Set[str]) -> None: self.create_templategroup(tgroup) def _create_templategroups_pre_62_compat(self, tgroups: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None: - """Zabbix <6.2 template group compatibility fallback method. - Template groups don't exist in Zabbix <6.2, so we create host - groups to fulfill the same purpose. - - Creates template host groups for all groups in the siteadmin - mapping file with the configured template group prefix.""" + """Compatibility method for creating template groups on Zabbix <6.2. + + Because template groups do not exist in <6.2, we instead create + host groups with the given names. + + Args: + tgroups: A set of template group names to create. + """ existing_hgroup_names = set(h["name"] for h in existing_hostgroups) for tgroup in tgroups: if tgroup in existing_hgroup_names: