Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add template group creation #66

Merged
merged 23 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b79abfe
Create template groups
pederhan Oct 20, 2023
561d5e6
Check version before creating template groups
pederhan Oct 27, 2023
075d31d
Add ZabbixVersion.from_version_string docstring
pederhan Oct 30, 2023
89d90f4
Reorder [zabbix] config options, uncomment defaults
pederhan Oct 30, 2023
107359a
Add ZabbixVersion tests
pederhan Oct 30, 2023
6458ddc
Log reason for skipping template group creation
pederhan Oct 30, 2023
4c6bb12
Log group creation
pederhan Nov 1, 2023
b12a7f7
Change incorrect logging calls
pederhan Nov 3, 2023
0192c9e
Disable template group creation by default
pederhan Nov 3, 2023
1aff439
Add Zabbix <6.4 compatibility
pederhan Nov 3, 2023
c129571
Make `create_extra_hostgroups` consistent w/ rest
pederhan Nov 3, 2023
2e8c152
Add missing typing import
pederhan Nov 3, 2023
4059bfe
Revert "Make `create_extra_hostgroups` consistent w/ rest"
pederhan Nov 3, 2023
45dae1e
Only create template groups for siteadmin groups
pederhan Nov 3, 2023
2e2c887
Remove comment, whitespace
pederhan Nov 9, 2023
35e3c07
Use packaging.version, lower requirement to 6.2
pederhan Nov 14, 2023
b9ddb2f
Add Zabbix API version parsing tests
pederhan Nov 14, 2023
17c7aae
Remove unused imports
pederhan Nov 14, 2023
ca146b1
Update sample config comment
pederhan Nov 14, 2023
aca1298
Remove duplicate definition
pederhan Nov 15, 2023
87d6f1f
Add packaging dependency to pyproject.toml
pederhan Nov 22, 2023
5bc97b9
Update README
pederhan Mar 19, 2024
9f1840e
Update `_create_templategroups*` method docstrings
pederhan Mar 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 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.
Note: Only tested with Zabbix 6.0 and 6.4.

## Requirements

Expand Down Expand Up @@ -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
Expand Down
20 changes: 13 additions & 7 deletions config.sample.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,18 @@ 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 = []
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
# NOTE: will create host groups if enabled on Zabbix <6.2
create_templategroups = true
templategroup_prefix = "Templates-"

extra_siteadmin_hostgroup_prefixes = []

[source_collectors.mysource]
# Name of the source collector module without the .py extension
Expand Down Expand Up @@ -87,5 +93,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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies = [
"pyzabbix>=1.3.0",
"requests>=1.0.0",
"tomli>=2.0.0",
"packaging>=23.2",
]

[project.optional-dependencies]
Expand Down
40 changes: 40 additions & 0 deletions tests/test_version.py
Original file line number Diff line number Diff line change
@@ -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")
9 changes: 7 additions & 2 deletions zabbix_auto_config/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import logging
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple, Union
Expand Down Expand Up @@ -62,13 +64,16 @@ class ZabbixSettings(ConfigBaseModel):

hostgroup_source_prefix: str = "Source-"
hostgroup_importance_prefix: str = "Importance-"

create_templategroups: bool = False
templategroup_prefix: str = "Templates-"

# 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")
Expand Down
107 changes: 94 additions & 13 deletions zabbix_auto_config/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
import signal
import itertools
import queue
from typing import Dict, List, TYPE_CHECKING, Optional
from typing import Dict, List, TYPE_CHECKING, Optional, Set

from packaging.version import Version
import psycopg2
from pydantic import ValidationError
import pyzabbix
Expand Down Expand Up @@ -604,6 +605,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 = Version(ver)

def work(self):
start_time = time.time()
logging.info("Zabbix update starting")
Expand Down Expand Up @@ -983,8 +987,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)
Expand Down Expand Up @@ -1064,24 +1068,29 @@ 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)
groupid = result["groupids"][0]
logging.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
)
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(
Expand All @@ -1094,6 +1103,74 @@ def create_extra_hostgroups(
continue
self.create_hostgroup(hostgroup)

def create_templategroup(self, templategroup_name: str) -> Optional[str]:
if self.config.dryrun:
logging.debug("DRYRUN: Creating template group: '%s'", templategroup_name)
return None

logging.debug("Creating template group: '%s'", templategroup_name)
try:
result = self.api.templategroup.create(name=templategroup_name)
groupid = result["groupids"][0]
logging.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",
templategroup_name,
e.args,
)
return None

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.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(
utils.with_prefix(tg, self.config.templategroup_prefix)
for tg in itertools.chain.from_iterable(
self.siteadmin_hostgroup_map.values()
)
)
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_62_compat(tgroups, existing_hostgroups)


def _create_templategroups(self, tgroups: Set[str]) -> None:
"""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:
if tgroup in existing_tgroups:
continue
self.create_templategroup(tgroup)

def _create_templategroups_pre_62_compat(self, tgroups: Set[str], existing_hostgroups: List[Dict[str, str]]) -> None:
"""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:
continue
self.create_hostgroup(tgroup)

def do_update(self):
managed_hostgroup_names = set(
itertools.chain.from_iterable(self.property_hostgroup_map.values())
Expand All @@ -1107,6 +1184,10 @@ 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 enabled
if self.config.create_templategroups:
self.create_templategroups(existing_hostgroups)

zabbix_hostgroups = {}
for zabbix_hostgroup in existing_hostgroups:
Expand Down
Loading