zbxtemplar 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zbxtemplar-0.1.0/LICENSE +21 -0
- zbxtemplar-0.1.0/PKG-INFO +229 -0
- zbxtemplar-0.1.0/README.md +189 -0
- zbxtemplar-0.1.0/pyproject.toml +34 -0
- zbxtemplar-0.1.0/setup.cfg +4 -0
- zbxtemplar-0.1.0/src/zbxtemplar/__init__.py +0 -0
- zbxtemplar-0.1.0/src/zbxtemplar/__main__.py +3 -0
- zbxtemplar-0.1.0/src/zbxtemplar/core/TemplarModule.py +47 -0
- zbxtemplar-0.1.0/src/zbxtemplar/core/ZbxEntity.py +165 -0
- zbxtemplar-0.1.0/src/zbxtemplar/core/__init__.py +1 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/Dashboard.py +116 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/DashboardWidget/ClassicGraph.py +19 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/DashboardWidget/SimpleGraph.py +20 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/DashboardWidget/__init__.py +1 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/Graph.py +99 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/Item.py +65 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/Template.py +27 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/Trigger.py +23 -0
- zbxtemplar-0.1.0/src/zbxtemplar/entities/__init__.py +6 -0
- zbxtemplar-0.1.0/src/zbxtemplar/main.py +36 -0
- zbxtemplar-0.1.0/src/zbxtemplar.egg-info/PKG-INFO +229 -0
- zbxtemplar-0.1.0/src/zbxtemplar.egg-info/SOURCES.txt +24 -0
- zbxtemplar-0.1.0/src/zbxtemplar.egg-info/dependency_links.txt +1 -0
- zbxtemplar-0.1.0/src/zbxtemplar.egg-info/entry_points.txt +2 -0
- zbxtemplar-0.1.0/src/zbxtemplar.egg-info/requires.txt +1 -0
- zbxtemplar-0.1.0/src/zbxtemplar.egg-info/top_level.txt +1 -0
zbxtemplar-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aleksei Dorozhkin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, 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,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zbxtemplar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Programmatic Zabbix template generation — Monitoring as Code
|
|
5
|
+
Author: Aleksei Dorozhkin
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Aleksei Dorozhkin
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/mithraelle/zbxtemplar
|
|
29
|
+
Project-URL: Bug Tracker, https://github.com/mithraelle/zbxtemplar/issues
|
|
30
|
+
Classifier: Development Status :: 4 - Beta
|
|
31
|
+
Classifier: Intended Audience :: System Administrators
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Programming Language :: Python :: 3
|
|
34
|
+
Classifier: Topic :: System :: Monitoring
|
|
35
|
+
Requires-Python: >=3.10
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
License-File: LICENSE
|
|
38
|
+
Requires-Dist: pyyaml>=6.0
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# zbxtemplar
|
|
42
|
+
|
|
43
|
+
A Pythonic framework for programmatic Zabbix Template generation (Monitoring as Code).
|
|
44
|
+
|
|
45
|
+
The goal is to cover the essential Zabbix configuration primitives — not every possible option. If you need a field that isn't exposed, raw dicts and string expressions give you an escape hatch.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install .
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Core Architecture
|
|
54
|
+
|
|
55
|
+
The project follows the `src-layout`:
|
|
56
|
+
|
|
57
|
+
- `zbxtemplar.entities` — Domain models: Template, Item, Trigger, Graph, Dashboard.
|
|
58
|
+
- `zbxtemplar.core` — Module contract (`TemplarModule`), loader, serialization, shared types.
|
|
59
|
+
- `zbxtemplar.main` — CLI entry point.
|
|
60
|
+
|
|
61
|
+
## Module Contract
|
|
62
|
+
|
|
63
|
+
Templates are defined as Python classes that inherit from `TemplarModule`.
|
|
64
|
+
The constructor is the contract — all template logic lives in `__init__`.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from zbxtemplar.core import TemplarModule
|
|
68
|
+
from zbxtemplar.entities import Template, Item, Trigger, TriggerPriority, Graph
|
|
69
|
+
|
|
70
|
+
class MyTemplate(TemplarModule):
|
|
71
|
+
def __init__(self):
|
|
72
|
+
super().__init__()
|
|
73
|
+
|
|
74
|
+
template = Template(name="My Service")
|
|
75
|
+
template.add_tag("Service", "MyApp")
|
|
76
|
+
template.add_macro("THRESHOLD", 90, "Alert threshold")
|
|
77
|
+
|
|
78
|
+
item = Item("CPU Usage", "system.cpu.util")
|
|
79
|
+
item.add_trigger("High CPU", "last", ">",
|
|
80
|
+
template.get_macro("THRESHOLD"),
|
|
81
|
+
priority=TriggerPriority.HIGH)
|
|
82
|
+
template.add_item(item)
|
|
83
|
+
|
|
84
|
+
self.templates = [template]
|
|
85
|
+
self.triggers = []
|
|
86
|
+
self.graphs = []
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A module file can contain multiple `TemplarModule` subclasses.
|
|
90
|
+
The loader discovers all of them by class name.
|
|
91
|
+
|
|
92
|
+
### Running standalone
|
|
93
|
+
|
|
94
|
+
Add a `__main__` guard to run the module directly:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
import yaml
|
|
99
|
+
module = MyTemplate()
|
|
100
|
+
print(yaml.dump(module.to_export(), default_flow_style=False, sort_keys=False))
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## CLI
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Basic usage
|
|
107
|
+
zbxtemplar module.py output.yml
|
|
108
|
+
python -m zbxtemplar module.py output.yml
|
|
109
|
+
|
|
110
|
+
# With options
|
|
111
|
+
zbxtemplar module.py output.yml \
|
|
112
|
+
--namespace "My Company" \
|
|
113
|
+
--template-group "Custom Templates"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Argument | Description |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `module` | Path to a `.py` file with `TemplarModule` subclass(es) |
|
|
119
|
+
| `output` | Output YAML file path |
|
|
120
|
+
| `--namespace` | UUID namespace for deterministic ID generation |
|
|
121
|
+
| `--template-group` | Default template group name |
|
|
122
|
+
|
|
123
|
+
## Programmatic Loading
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from zbxtemplar.core import load_module
|
|
127
|
+
|
|
128
|
+
modules = load_module("path/to/module.py")
|
|
129
|
+
# Returns {"ClassName": <instance>, ...}
|
|
130
|
+
|
|
131
|
+
for name, mod in modules.items():
|
|
132
|
+
export = mod.to_export() # Full zabbix_export dict
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Entities Reference
|
|
136
|
+
|
|
137
|
+
### Template
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
template = Template(name="My Template", groups=["Custom Group"])
|
|
141
|
+
template.add_tag("Service", "MyApp")
|
|
142
|
+
template.add_macro("TIMEOUT", 30, "Connection timeout")
|
|
143
|
+
template.add_item(item)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Item
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
item = Item("CPU Usage", "system.cpu.util")
|
|
150
|
+
|
|
151
|
+
# Expression helper — builds Zabbix function strings
|
|
152
|
+
item.expr("last") # last(/host/key)
|
|
153
|
+
item.expr("min", "10m") # min(/host/key,10m)
|
|
154
|
+
item.expr("count", "#10") # count(/host/key,#10)
|
|
155
|
+
|
|
156
|
+
# Single-item trigger shorthand
|
|
157
|
+
item.add_trigger("High CPU", "last", ">", 90,
|
|
158
|
+
priority=TriggerPriority.HIGH,
|
|
159
|
+
description="CPU threshold exceeded")
|
|
160
|
+
|
|
161
|
+
# With function args
|
|
162
|
+
item.add_trigger("Sustained high", "min", ">", 100,
|
|
163
|
+
fn_args=("10m",), priority=TriggerPriority.WARNING)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
All enum values (`ItemType`, `ValueType`, `TriggerPriority`, `GraphType`, `DrawType`, `CalcFnc`, etc.) follow the Zabbix export format documentation.
|
|
167
|
+
|
|
168
|
+
### Trigger
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
# Standalone (multi-item) trigger — raw expression string
|
|
172
|
+
Trigger(name="Complex alert",
|
|
173
|
+
expression=item1.expr("last") + ">0 and " + item2.expr("min", "5m") + "<100",
|
|
174
|
+
priority=TriggerPriority.WARNING,
|
|
175
|
+
description="Multi-item condition")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Macro
|
|
179
|
+
|
|
180
|
+
Macros work seamlessly in string contexts:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
template.add_macro("MY_MACRO", 1, "Description")
|
|
184
|
+
macro = template.get_macro("MY_MACRO")
|
|
185
|
+
|
|
186
|
+
str(macro) # {$MY_MACRO}
|
|
187
|
+
item.expr("last") + ">" + macro # last(/host/key)>{$MY_MACRO}
|
|
188
|
+
item.add_trigger("Alert", "last", ">", macro) # works as threshold
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Graph
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
graph = Graph("CPU Graph",
|
|
195
|
+
graph_type=GraphType.STACKED,
|
|
196
|
+
y_min_type=YAxisType.FIXED, y_min=0,
|
|
197
|
+
y_max_type=YAxisType.FIXED, y_max=100)
|
|
198
|
+
|
|
199
|
+
graph.add_item(item1, "FF0000")
|
|
200
|
+
graph.add_item(item2, "00FF00",
|
|
201
|
+
drawtype=DrawType.BOLD_LINE,
|
|
202
|
+
calc_fnc=CalcFnc.MAX,
|
|
203
|
+
yaxisside=YAxisSide.RIGHT)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Dashboard
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from zbxtemplar.entities import Dashboard, DashboardPage
|
|
210
|
+
from zbxtemplar.entities.DashboardWidget import ClassicGraph
|
|
211
|
+
|
|
212
|
+
page = DashboardPage(name="Overview", display_period=120)
|
|
213
|
+
page.add_widget(ClassicGraph(host=template.name, graph=graph, width=36, height=5))
|
|
214
|
+
|
|
215
|
+
dashboard = Dashboard("My Dashboard", display_period=60, auto_start=YesNo.NO)
|
|
216
|
+
dashboard.add_page(page)
|
|
217
|
+
template.add_dashboard(dashboard)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Widgets are concrete subclasses of `Widget` (abstract). Each widget type lives in `zbxtemplar.entities.DashboardWidget`. Creating custom widgets is straightforward — subclass `Widget`, define `type` and `widget_fields()`.
|
|
221
|
+
|
|
222
|
+
## Global Configuration
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from zbxtemplar.core.ZbxEntity import set_uuid_namespace, set_template_group
|
|
226
|
+
|
|
227
|
+
set_uuid_namespace("My Company") # Deterministic UUIDs scoped to namespace
|
|
228
|
+
set_template_group("My Templates") # Default group for new templates
|
|
229
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# zbxtemplar
|
|
2
|
+
|
|
3
|
+
A Pythonic framework for programmatic Zabbix Template generation (Monitoring as Code).
|
|
4
|
+
|
|
5
|
+
The goal is to cover the essential Zabbix configuration primitives — not every possible option. If you need a field that isn't exposed, raw dicts and string expressions give you an escape hatch.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Core Architecture
|
|
14
|
+
|
|
15
|
+
The project follows the `src-layout`:
|
|
16
|
+
|
|
17
|
+
- `zbxtemplar.entities` — Domain models: Template, Item, Trigger, Graph, Dashboard.
|
|
18
|
+
- `zbxtemplar.core` — Module contract (`TemplarModule`), loader, serialization, shared types.
|
|
19
|
+
- `zbxtemplar.main` — CLI entry point.
|
|
20
|
+
|
|
21
|
+
## Module Contract
|
|
22
|
+
|
|
23
|
+
Templates are defined as Python classes that inherit from `TemplarModule`.
|
|
24
|
+
The constructor is the contract — all template logic lives in `__init__`.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from zbxtemplar.core import TemplarModule
|
|
28
|
+
from zbxtemplar.entities import Template, Item, Trigger, TriggerPriority, Graph
|
|
29
|
+
|
|
30
|
+
class MyTemplate(TemplarModule):
|
|
31
|
+
def __init__(self):
|
|
32
|
+
super().__init__()
|
|
33
|
+
|
|
34
|
+
template = Template(name="My Service")
|
|
35
|
+
template.add_tag("Service", "MyApp")
|
|
36
|
+
template.add_macro("THRESHOLD", 90, "Alert threshold")
|
|
37
|
+
|
|
38
|
+
item = Item("CPU Usage", "system.cpu.util")
|
|
39
|
+
item.add_trigger("High CPU", "last", ">",
|
|
40
|
+
template.get_macro("THRESHOLD"),
|
|
41
|
+
priority=TriggerPriority.HIGH)
|
|
42
|
+
template.add_item(item)
|
|
43
|
+
|
|
44
|
+
self.templates = [template]
|
|
45
|
+
self.triggers = []
|
|
46
|
+
self.graphs = []
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A module file can contain multiple `TemplarModule` subclasses.
|
|
50
|
+
The loader discovers all of them by class name.
|
|
51
|
+
|
|
52
|
+
### Running standalone
|
|
53
|
+
|
|
54
|
+
Add a `__main__` guard to run the module directly:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
import yaml
|
|
59
|
+
module = MyTemplate()
|
|
60
|
+
print(yaml.dump(module.to_export(), default_flow_style=False, sort_keys=False))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## CLI
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Basic usage
|
|
67
|
+
zbxtemplar module.py output.yml
|
|
68
|
+
python -m zbxtemplar module.py output.yml
|
|
69
|
+
|
|
70
|
+
# With options
|
|
71
|
+
zbxtemplar module.py output.yml \
|
|
72
|
+
--namespace "My Company" \
|
|
73
|
+
--template-group "Custom Templates"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Argument | Description |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `module` | Path to a `.py` file with `TemplarModule` subclass(es) |
|
|
79
|
+
| `output` | Output YAML file path |
|
|
80
|
+
| `--namespace` | UUID namespace for deterministic ID generation |
|
|
81
|
+
| `--template-group` | Default template group name |
|
|
82
|
+
|
|
83
|
+
## Programmatic Loading
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from zbxtemplar.core import load_module
|
|
87
|
+
|
|
88
|
+
modules = load_module("path/to/module.py")
|
|
89
|
+
# Returns {"ClassName": <instance>, ...}
|
|
90
|
+
|
|
91
|
+
for name, mod in modules.items():
|
|
92
|
+
export = mod.to_export() # Full zabbix_export dict
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Entities Reference
|
|
96
|
+
|
|
97
|
+
### Template
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
template = Template(name="My Template", groups=["Custom Group"])
|
|
101
|
+
template.add_tag("Service", "MyApp")
|
|
102
|
+
template.add_macro("TIMEOUT", 30, "Connection timeout")
|
|
103
|
+
template.add_item(item)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Item
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
item = Item("CPU Usage", "system.cpu.util")
|
|
110
|
+
|
|
111
|
+
# Expression helper — builds Zabbix function strings
|
|
112
|
+
item.expr("last") # last(/host/key)
|
|
113
|
+
item.expr("min", "10m") # min(/host/key,10m)
|
|
114
|
+
item.expr("count", "#10") # count(/host/key,#10)
|
|
115
|
+
|
|
116
|
+
# Single-item trigger shorthand
|
|
117
|
+
item.add_trigger("High CPU", "last", ">", 90,
|
|
118
|
+
priority=TriggerPriority.HIGH,
|
|
119
|
+
description="CPU threshold exceeded")
|
|
120
|
+
|
|
121
|
+
# With function args
|
|
122
|
+
item.add_trigger("Sustained high", "min", ">", 100,
|
|
123
|
+
fn_args=("10m",), priority=TriggerPriority.WARNING)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
All enum values (`ItemType`, `ValueType`, `TriggerPriority`, `GraphType`, `DrawType`, `CalcFnc`, etc.) follow the Zabbix export format documentation.
|
|
127
|
+
|
|
128
|
+
### Trigger
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# Standalone (multi-item) trigger — raw expression string
|
|
132
|
+
Trigger(name="Complex alert",
|
|
133
|
+
expression=item1.expr("last") + ">0 and " + item2.expr("min", "5m") + "<100",
|
|
134
|
+
priority=TriggerPriority.WARNING,
|
|
135
|
+
description="Multi-item condition")
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Macro
|
|
139
|
+
|
|
140
|
+
Macros work seamlessly in string contexts:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
template.add_macro("MY_MACRO", 1, "Description")
|
|
144
|
+
macro = template.get_macro("MY_MACRO")
|
|
145
|
+
|
|
146
|
+
str(macro) # {$MY_MACRO}
|
|
147
|
+
item.expr("last") + ">" + macro # last(/host/key)>{$MY_MACRO}
|
|
148
|
+
item.add_trigger("Alert", "last", ">", macro) # works as threshold
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Graph
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
graph = Graph("CPU Graph",
|
|
155
|
+
graph_type=GraphType.STACKED,
|
|
156
|
+
y_min_type=YAxisType.FIXED, y_min=0,
|
|
157
|
+
y_max_type=YAxisType.FIXED, y_max=100)
|
|
158
|
+
|
|
159
|
+
graph.add_item(item1, "FF0000")
|
|
160
|
+
graph.add_item(item2, "00FF00",
|
|
161
|
+
drawtype=DrawType.BOLD_LINE,
|
|
162
|
+
calc_fnc=CalcFnc.MAX,
|
|
163
|
+
yaxisside=YAxisSide.RIGHT)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Dashboard
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from zbxtemplar.entities import Dashboard, DashboardPage
|
|
170
|
+
from zbxtemplar.entities.DashboardWidget import ClassicGraph
|
|
171
|
+
|
|
172
|
+
page = DashboardPage(name="Overview", display_period=120)
|
|
173
|
+
page.add_widget(ClassicGraph(host=template.name, graph=graph, width=36, height=5))
|
|
174
|
+
|
|
175
|
+
dashboard = Dashboard("My Dashboard", display_period=60, auto_start=YesNo.NO)
|
|
176
|
+
dashboard.add_page(page)
|
|
177
|
+
template.add_dashboard(dashboard)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Widgets are concrete subclasses of `Widget` (abstract). Each widget type lives in `zbxtemplar.entities.DashboardWidget`. Creating custom widgets is straightforward — subclass `Widget`, define `type` and `widget_fields()`.
|
|
181
|
+
|
|
182
|
+
## Global Configuration
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from zbxtemplar.core.ZbxEntity import set_uuid_namespace, set_template_group
|
|
186
|
+
|
|
187
|
+
set_uuid_namespace("My Company") # Deterministic UUIDs scoped to namespace
|
|
188
|
+
set_template_group("My Templates") # Default group for new templates
|
|
189
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "zbxtemplar"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Programmatic Zabbix template generation — Monitoring as Code"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {file = "LICENSE"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Aleksei Dorozhkin"}
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: System :: Monitoring",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"pyyaml>=6.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
"Homepage" = "https://github.com/mithraelle/zbxtemplar"
|
|
28
|
+
"Bug Tracker" = "https://github.com/mithraelle/zbxtemplar/issues"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
zbxtemplar = "zbxtemplar.main:main"
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import importlib.util
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import zbxtemplar.core.ZbxEntity as _zbx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TemplarModule:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.templates = []
|
|
11
|
+
self.triggers = []
|
|
12
|
+
self.graphs = []
|
|
13
|
+
|
|
14
|
+
def to_export(self, version: str = "7.4") -> dict:
|
|
15
|
+
groups = set()
|
|
16
|
+
for t in self.templates:
|
|
17
|
+
for g in t.groups:
|
|
18
|
+
groups.add(g["name"])
|
|
19
|
+
|
|
20
|
+
export = {
|
|
21
|
+
"zabbix_export": {
|
|
22
|
+
"version": version,
|
|
23
|
+
"template_groups": [
|
|
24
|
+
{"uuid": _zbx._make_uuid(g), "name": g}
|
|
25
|
+
for g in sorted(groups)
|
|
26
|
+
],
|
|
27
|
+
"templates": [t.to_dict() for t in self.templates],
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if self.triggers:
|
|
31
|
+
export["zabbix_export"]["triggers"] = [t.to_dict() for t in self.triggers]
|
|
32
|
+
if self.graphs:
|
|
33
|
+
export["zabbix_export"]["graphs"] = [g.to_dict() for g in self.graphs]
|
|
34
|
+
return export
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def load_module(filename: str) -> dict:
|
|
38
|
+
mod_name = os.path.splitext(os.path.basename(filename))[0]
|
|
39
|
+
spec = importlib.util.spec_from_file_location(mod_name, filename)
|
|
40
|
+
mod = importlib.util.module_from_spec(spec)
|
|
41
|
+
spec.loader.exec_module(mod)
|
|
42
|
+
|
|
43
|
+
result = {}
|
|
44
|
+
for name, obj in inspect.getmembers(mod, inspect.isclass):
|
|
45
|
+
if issubclass(obj, TemplarModule) and obj is not TemplarModule:
|
|
46
|
+
result[name] = obj()
|
|
47
|
+
return result
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from typing import Any, List, Optional, Dict, Union
|
|
2
|
+
from enum import Enum
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
class YesNo(str, Enum):
|
|
6
|
+
NO = "NO"
|
|
7
|
+
YES = "YES"
|
|
8
|
+
|
|
9
|
+
ZBX_TEMPLAR_NAMESPACE = "Zbx Templar"
|
|
10
|
+
ZBX_TEMPLAR_TEMPLATE_GROUP = "Templar Templates"
|
|
11
|
+
_NAMESPACE_UUID = uuid.uuid5(uuid.NAMESPACE_DNS, ZBX_TEMPLAR_NAMESPACE)
|
|
12
|
+
|
|
13
|
+
def set_uuid_namespace(namespace: str):
|
|
14
|
+
global ZBX_TEMPLAR_NAMESPACE, _NAMESPACE_UUID
|
|
15
|
+
ZBX_TEMPLAR_NAMESPACE = namespace
|
|
16
|
+
_NAMESPACE_UUID = uuid.uuid5(uuid.NAMESPACE_DNS, namespace)
|
|
17
|
+
|
|
18
|
+
def set_template_group(group: str):
|
|
19
|
+
global ZBX_TEMPLAR_TEMPLATE_GROUP
|
|
20
|
+
ZBX_TEMPLAR_TEMPLATE_GROUP = group
|
|
21
|
+
|
|
22
|
+
def _make_uuid(seed: str) -> str:
|
|
23
|
+
"""Deterministic UUID from seed, masked as UUIDv4. Zabbix rejects UUID5 on import."""
|
|
24
|
+
h = uuid.uuid5(_NAMESPACE_UUID, seed).hex
|
|
25
|
+
return h[:12] + '4' + h[13:16] + hex(0x8 | (int(h[16], 16) & 0x3))[2:] + h[17:]
|
|
26
|
+
|
|
27
|
+
def _serialize(value):
|
|
28
|
+
if hasattr(value, 'to_dict'):
|
|
29
|
+
return value.to_dict()
|
|
30
|
+
if isinstance(value, dict):
|
|
31
|
+
return {k: _serialize(v) for k, v in value.items()}
|
|
32
|
+
if isinstance(value, list):
|
|
33
|
+
return [_serialize(v) for v in value]
|
|
34
|
+
if isinstance(value, Enum):
|
|
35
|
+
return value.value
|
|
36
|
+
if hasattr(value, '__dict__') and not isinstance(value, (str, int, float, bool)):
|
|
37
|
+
return {k: _serialize(v) for k, v in value.__dict__.items()}
|
|
38
|
+
return value
|
|
39
|
+
|
|
40
|
+
class ZbxEntity:
|
|
41
|
+
def __init__(self, name: str, uuid_seed: str = None):
|
|
42
|
+
super().__init__()
|
|
43
|
+
self.name = name
|
|
44
|
+
self.uuid = _make_uuid(uuid_seed or name)
|
|
45
|
+
|
|
46
|
+
def to_dict(self) -> dict[str, Any]:
|
|
47
|
+
# Attributes starting with _ are internal-only (e.g. _host) and skipped here
|
|
48
|
+
result = {}
|
|
49
|
+
for key, value in self.__dict__.items():
|
|
50
|
+
if key.startswith('_'):
|
|
51
|
+
continue
|
|
52
|
+
if value is None or value == {} or value == []:
|
|
53
|
+
continue
|
|
54
|
+
if key == 'tags' and hasattr(self, 'tags_to_list'):
|
|
55
|
+
result[key] = self.tags_to_list()
|
|
56
|
+
elif key == 'macros' and hasattr(self, 'macros_to_list'):
|
|
57
|
+
result[key] = self.macros_to_list()
|
|
58
|
+
else:
|
|
59
|
+
result[key] = _serialize(value)
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
class Tag:
|
|
63
|
+
def __init__(self, name: str, value: str = ""):
|
|
64
|
+
self.name = name
|
|
65
|
+
self.value = value
|
|
66
|
+
|
|
67
|
+
def to_dict(self):
|
|
68
|
+
return {"tag": self.name, "value": self.value}
|
|
69
|
+
|
|
70
|
+
class WithTags():
|
|
71
|
+
def __init__(self):
|
|
72
|
+
super().__init__()
|
|
73
|
+
self.tags: Dict[str, Tag] = {}
|
|
74
|
+
|
|
75
|
+
def add_tag(self, tag: str, value: str = ""):
|
|
76
|
+
self.tags[tag] = Tag(name=tag, value=value)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def tags_to_list(self):
|
|
80
|
+
return [t.to_dict() for t in self.tags.values()]
|
|
81
|
+
|
|
82
|
+
def load_tags(self, tags: Dict[str, str]):
|
|
83
|
+
"""Load tags from a dict.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
tags: Mapping of tag name to value.
|
|
87
|
+
Example: ``{"Application": "CPU", "Component": "OS"}``
|
|
88
|
+
"""
|
|
89
|
+
for tag, value in tags.items():
|
|
90
|
+
self.add_tag(tag, value)
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
class Macro():
|
|
94
|
+
def __init__(self, name: str, value: str, description: Optional[str] = None):
|
|
95
|
+
self.name = name
|
|
96
|
+
self.value = value
|
|
97
|
+
self.description = description
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def full_name(self) -> str:
|
|
101
|
+
return f"{{${self.name}}}"
|
|
102
|
+
|
|
103
|
+
def __str__(self):
|
|
104
|
+
return self.full_name
|
|
105
|
+
|
|
106
|
+
def __add__(self, other):
|
|
107
|
+
return str(self) + other
|
|
108
|
+
|
|
109
|
+
def __radd__(self, other):
|
|
110
|
+
return other + str(self)
|
|
111
|
+
|
|
112
|
+
def to_dict(self):
|
|
113
|
+
result = {"macro": self.full_name, "value": self.value}
|
|
114
|
+
if self.description is not None:
|
|
115
|
+
result["description"] = self.description
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
class WithMacros():
|
|
119
|
+
def __init__(self):
|
|
120
|
+
super().__init__()
|
|
121
|
+
self.macros: Dict[str, Macro] = {}
|
|
122
|
+
|
|
123
|
+
def add_macro(self, name: str, value: Union[str, int], description: Union[str, None] = None):
|
|
124
|
+
clean_name = name.replace("{$", "").replace("}", "")
|
|
125
|
+
self.macros[clean_name] = Macro(name=clean_name, value=str(value), description=description)
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def load_macros(self, macros: Union[Dict[str, Union[str, tuple]], List[dict]]):
|
|
129
|
+
"""Load macros from a dict or a list of dicts.
|
|
130
|
+
|
|
131
|
+
Accepts two formats:
|
|
132
|
+
|
|
133
|
+
**Dict** — for programmatic use::
|
|
134
|
+
|
|
135
|
+
load_macros({"TIMEOUT": "30"})
|
|
136
|
+
load_macros({"TIMEOUT": ("30", "Connection timeout")})
|
|
137
|
+
|
|
138
|
+
**List of dicts** — for data loaded from YAML/XML::
|
|
139
|
+
|
|
140
|
+
load_macros([
|
|
141
|
+
{"name": "TIMEOUT", "value": "30", "description": "Connection timeout"}
|
|
142
|
+
])
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
macros: Either a ``{name: value}`` / ``{name: (value, description)}`` dict,
|
|
146
|
+
or a list of dicts with ``name``, ``value``, and optional ``description`` keys.
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(macros, dict):
|
|
149
|
+
for name, value in macros.items():
|
|
150
|
+
if isinstance(value, tuple):
|
|
151
|
+
self.add_macro(name, value[0], value[1] if len(value) > 1 else None)
|
|
152
|
+
else:
|
|
153
|
+
self.add_macro(name, value)
|
|
154
|
+
else:
|
|
155
|
+
for entry in macros:
|
|
156
|
+
self.add_macro(entry["name"], entry["value"], entry.get("description"))
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def macros_to_list(self):
|
|
160
|
+
return [m.to_dict() for m in self.macros.values()]
|
|
161
|
+
|
|
162
|
+
def get_macro(self, name: str) -> Optional[Macro]:
|
|
163
|
+
clean_name = name.replace("{$", "").replace("}", "")
|
|
164
|
+
return self.macros.get(clean_name)
|
|
165
|
+
|