zabbix-crontroller 0.0.1__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.
- zabbix_crontroller-0.0.1/.gitignore +218 -0
- zabbix_crontroller-0.0.1/CONTRIBUTE.md +151 -0
- zabbix_crontroller-0.0.1/LICENSE +21 -0
- zabbix_crontroller-0.0.1/PKG-INFO +235 -0
- zabbix_crontroller-0.0.1/README.md +211 -0
- zabbix_crontroller-0.0.1/docs/RUNTIME.md +216 -0
- zabbix_crontroller-0.0.1/docs/SYNTAX_SPECIFICATION.md +498 -0
- zabbix_crontroller-0.0.1/pyproject.toml +54 -0
- zabbix_crontroller-0.0.1/src/zabbix_crontroller/__init__.py +8 -0
- zabbix_crontroller-0.0.1/src/zabbix_crontroller/app.py +596 -0
- zabbix_crontroller-0.0.1/src/zabbix_crontroller/invocation.py +150 -0
- zabbix_crontroller-0.0.1/src/zabbix_crontroller/parser.py +466 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
# Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
# poetry.lock
|
|
109
|
+
# poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
# pdm.lock
|
|
116
|
+
# pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
# pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# Redis
|
|
135
|
+
*.rdb
|
|
136
|
+
*.aof
|
|
137
|
+
*.pid
|
|
138
|
+
|
|
139
|
+
# RabbitMQ
|
|
140
|
+
mnesia/
|
|
141
|
+
rabbitmq/
|
|
142
|
+
rabbitmq-data/
|
|
143
|
+
|
|
144
|
+
# ActiveMQ
|
|
145
|
+
activemq-data/
|
|
146
|
+
|
|
147
|
+
# SageMath parsed files
|
|
148
|
+
*.sage.py
|
|
149
|
+
|
|
150
|
+
# Environments
|
|
151
|
+
.env
|
|
152
|
+
.envrc
|
|
153
|
+
.venv
|
|
154
|
+
env/
|
|
155
|
+
venv/
|
|
156
|
+
ENV/
|
|
157
|
+
env.bak/
|
|
158
|
+
venv.bak/
|
|
159
|
+
|
|
160
|
+
# Spyder project settings
|
|
161
|
+
.spyderproject
|
|
162
|
+
.spyproject
|
|
163
|
+
|
|
164
|
+
# Rope project settings
|
|
165
|
+
.ropeproject
|
|
166
|
+
|
|
167
|
+
# mkdocs documentation
|
|
168
|
+
/site
|
|
169
|
+
|
|
170
|
+
# mypy
|
|
171
|
+
.mypy_cache/
|
|
172
|
+
.dmypy.json
|
|
173
|
+
dmypy.json
|
|
174
|
+
|
|
175
|
+
# Pyre type checker
|
|
176
|
+
.pyre/
|
|
177
|
+
|
|
178
|
+
# pytype static type analyzer
|
|
179
|
+
.pytype/
|
|
180
|
+
|
|
181
|
+
# Cython debug symbols
|
|
182
|
+
cython_debug/
|
|
183
|
+
|
|
184
|
+
# PyCharm
|
|
185
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
186
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
188
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
189
|
+
# .idea/
|
|
190
|
+
|
|
191
|
+
# Abstra
|
|
192
|
+
# Abstra is an AI-powered process automation framework.
|
|
193
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
194
|
+
# Learn more at https://abstra.io/docs
|
|
195
|
+
.abstra/
|
|
196
|
+
|
|
197
|
+
# Visual Studio Code
|
|
198
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
199
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
+
# .vscode/
|
|
203
|
+
# Temporary file for partial code execution
|
|
204
|
+
tempCodeRunnerFile.py
|
|
205
|
+
|
|
206
|
+
# Ruff stuff:
|
|
207
|
+
.ruff_cache/
|
|
208
|
+
|
|
209
|
+
# PyPI configuration file
|
|
210
|
+
.pypirc
|
|
211
|
+
|
|
212
|
+
# Marimo
|
|
213
|
+
marimo/_static/
|
|
214
|
+
marimo/_lsp/
|
|
215
|
+
__marimo__/
|
|
216
|
+
|
|
217
|
+
# Streamlit
|
|
218
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use Python 3.14 or later and uv. The deployment target is Linux with cron
|
|
4
|
+
installed; other operating systems are not supported deployment targets.
|
|
5
|
+
|
|
6
|
+
## Development setup
|
|
7
|
+
|
|
8
|
+
Install the package and run all tests:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
uv sync
|
|
12
|
+
uv run python -m unittest discover -s tests -v
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The tests use Python's standard library. They cover the public parser API,
|
|
16
|
+
documentation and repository examples, importing the package from outside the
|
|
17
|
+
repository, and actual wrapper subprocesses. Runtime tests exercise byte capture,
|
|
18
|
+
signals, concurrent runs, retention and injected storage/forwarding failures.
|
|
19
|
+
All tests must pass before a change is accepted. Linux is the deployment target;
|
|
20
|
+
passing tests on another POSIX platform does not establish cron compatibility.
|
|
21
|
+
|
|
22
|
+
Use `uv run zabbix-crontroller --help` to exercise the installed entry point. The
|
|
23
|
+
wrapper is also available as `uv run python -m zabbix_crontroller.app`. Test run
|
|
24
|
+
evidence belongs in temporary directories, with `ZC_STATE_DIR` set explicitly.
|
|
25
|
+
See [runtime behaviour](docs/RUNTIME.md) for the evidence format and a Linux cron
|
|
26
|
+
integration procedure. Do not install a test crontab over a user's real jobs.
|
|
27
|
+
|
|
28
|
+
## Automated Linux checks
|
|
29
|
+
|
|
30
|
+
The CI workflow runs on branch pushes and pull requests that change a `.py` file
|
|
31
|
+
at any depth, `.python-version`, `LICENSE`, `pyproject.toml` or `uv.lock`. Every
|
|
32
|
+
matching run executes the full suite, including `LICENSE`-only changes.
|
|
33
|
+
Documentation-only and workflow-only changes do not trigger CI, as required by
|
|
34
|
+
[AGENTS.md](AGENTS.md). A separate tag-push workflow always runs the same checks,
|
|
35
|
+
regardless of changed paths, before building release packages.
|
|
36
|
+
|
|
37
|
+
The shared [Linux workflow](.github/workflows/linux-tests.yml) runs Black and
|
|
38
|
+
tests these distribution families on GitHub's Ubuntu x86-64 Docker runners:
|
|
39
|
+
|
|
40
|
+
| Distribution | Container base | Cron package |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Ubuntu 24.04 LTS | `ubuntu:24.04` | `cron` |
|
|
43
|
+
| Debian 13 | `debian:13-slim` | `cron` |
|
|
44
|
+
| Fedora 44 | `fedora:44` | `cronie` |
|
|
45
|
+
| Rocky Linux 9 | `rockylinux:9` | `cronie` |
|
|
46
|
+
| openSUSE Leap 16.0 | `opensuse/leap:16.0` | `cronie` |
|
|
47
|
+
| Alpine 3.24 | `alpine:3.24` | `cronie` |
|
|
48
|
+
|
|
49
|
+
uv installs the latest patch of the Python version in `.python-version`, including
|
|
50
|
+
the musl build on Alpine. Alpine uses Cronie explicitly; these checks do not
|
|
51
|
+
establish compatibility with BusyBox crond. Each container runs every unittest
|
|
52
|
+
as root and `zc-test`, then starts the native cron daemon and waits for actual
|
|
53
|
+
minute-boundary dispatch. Three monitored jobs per account plus an unwrapped
|
|
54
|
+
control check exit status, stdout/stderr capture and forwarding, `%` handling,
|
|
55
|
+
stdin, environment, working directory, umask, shell selection, correlation IDs
|
|
56
|
+
and evidence permissions. Both executable installation locations and both
|
|
57
|
+
candidate-file and `crontab -l` validation are exercised.
|
|
58
|
+
|
|
59
|
+
The checkout is mounted read-only and copied into the disposable container. The
|
|
60
|
+
test installation is editable and uses a placeholder version; CI does not create
|
|
61
|
+
release distributions. The harness refuses to replace existing crontabs. The
|
|
62
|
+
Alpine image removes its stock maintenance crontab during image preparation.
|
|
63
|
+
Containers do not run systemd, so service-manager integration and host security
|
|
64
|
+
policies such as SELinux remain separate deployment checks.
|
|
65
|
+
|
|
66
|
+
To reproduce one matrix entry locally, run these commands from the repository
|
|
67
|
+
root with Docker available. Change `BASE_IMAGE` to another value from the table:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
docker build --pull --build-arg BASE_IMAGE=debian:13-slim \
|
|
71
|
+
--build-arg PYTHON_VERSION="$(cat .python-version)" \
|
|
72
|
+
--tag zc-linux-tests tests/linux
|
|
73
|
+
docker run --name zc-linux-tests --init \
|
|
74
|
+
--mount "type=bind,source=$PWD,target=/source,readonly" zc-linux-tests
|
|
75
|
+
# Collect evidence even if the preceding test command failed.
|
|
76
|
+
mkdir -p /tmp/zc-linux-reports
|
|
77
|
+
docker cp zc-linux-tests:/reports/. /tmp/zc-linux-reports/
|
|
78
|
+
docker logs zc-linux-tests > /tmp/zc-linux-reports/container.log 2>&1
|
|
79
|
+
docker rm -f zc-linux-tests
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The workflow uploads `linux-DISTRIBUTION` diagnostic artefacts for 14 days, even
|
|
83
|
+
on failure. They contain the full unittest logs, cron configuration and daemon
|
|
84
|
+
log, captured run evidence and `result.json` with operating system, architecture,
|
|
85
|
+
Python and cron versions. Local Docker uses the host's architecture by default;
|
|
86
|
+
use `--platform linux/amd64` for both build and run to reproduce GitHub's target
|
|
87
|
+
on an ARM host. The cron check normally completes within a minute after the
|
|
88
|
+
suite; it fails after 100 seconds without the expected run evidence.
|
|
89
|
+
|
|
90
|
+
## Code and documentation
|
|
91
|
+
|
|
92
|
+
Follow PEP 8 and PEP 257, use Python docstrings, and keep implementations simple.
|
|
93
|
+
Prefer standard-library solutions where suitable. Write documentation, docstrings
|
|
94
|
+
and commit messages in British English.
|
|
95
|
+
|
|
96
|
+
Format Python changes with Black and check formatting before submitting:
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
uvx black src tests
|
|
100
|
+
uvx black --check src tests
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Follow the frozen [Syntax Specification](docs/SYNTAX_SPECIFICATION.md) and keep
|
|
104
|
+
the [Syntax User Guide](README.md#syntax), examples and tests consistent with it.
|
|
105
|
+
Changes to published syntax require a new syntax version; published versions
|
|
106
|
+
must remain supported. The required wrapper invocation was added to version `1.0`
|
|
107
|
+
before its first publication. Keep the parser importable independently of the
|
|
108
|
+
application; filesystem checks belong to `zc validate`, not `parse_crontab`.
|
|
109
|
+
|
|
110
|
+
## Commits and releases
|
|
111
|
+
|
|
112
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#specification).
|
|
113
|
+
The `master` branch is not guaranteed to be stable. Follow
|
|
114
|
+
[Semantic Versioning](https://semver.org) for package releases.
|
|
115
|
+
|
|
116
|
+
Build and publish release packages through GitHub workflows triggered by a pushed
|
|
117
|
+
tag. Those workflows build both source distributions and wheels, then publish
|
|
118
|
+
them to PyPI and GitHub Releases. Commits alone must not trigger release builds
|
|
119
|
+
or publishing. See [AGENTS.md](AGENTS.md) for repository workflow rules.
|
|
120
|
+
|
|
121
|
+
The [release workflow](.github/workflows/release.yml) accepts `vMAJOR.MINOR.PATCH`
|
|
122
|
+
tags, for example `v1.0.0`. Pre-release tags use `-alpha.N`, `-beta.N` or `-rc.N`,
|
|
123
|
+
for example `v1.0.0-rc.1`, which becomes Python package version `1.0.0rc1`.
|
|
124
|
+
Other tag formats fail before testing or building. Choose the version according
|
|
125
|
+
to SemVer and never move an already published release tag.
|
|
126
|
+
|
|
127
|
+
After the Linux matrix and Black pass, `uv build --sdist --wheel` builds both
|
|
128
|
+
distributions. Twine checks the metadata, and both package versions must match
|
|
129
|
+
the tag. The same uploaded artefacts are then published to PyPI and attached to
|
|
130
|
+
a GitHub Release with generated notes. Pre-releases are marked accordingly.
|
|
131
|
+
PyPI publishing uses short-lived OIDC credentials through
|
|
132
|
+
[Trusted Publishing](https://docs.pypi.org/trusted-publishers/), without an API
|
|
133
|
+
token secret. The PyPI job alone has `id-token: write`; the GitHub Release job
|
|
134
|
+
alone has `contents: write`. Publishing is never triggered by a branch or pull
|
|
135
|
+
request run.
|
|
136
|
+
|
|
137
|
+
Before the first release, a maintainer must:
|
|
138
|
+
|
|
139
|
+
1. Create the GitHub environment `pypi` and restrict its deployment policy to
|
|
140
|
+
release tags. Add reviewers if the project requires release approval.
|
|
141
|
+
2. Register a PyPI Trusted Publisher (or a pending publisher for a new project)
|
|
142
|
+
with owner `theriverman`, repository `zabbix-crontroller`, workflow filename
|
|
143
|
+
`release.yml`, and environment `pypi`.
|
|
144
|
+
3. Allow GitHub Actions in the repository and protect release tags with a
|
|
145
|
+
repository ruleset appropriate for the maintainers.
|
|
146
|
+
|
|
147
|
+
Retry failed publishing jobs from the existing workflow run so they reuse the
|
|
148
|
+
validated distributions. uv checks PyPI for identical existing files before
|
|
149
|
+
uploading; a conflicting file fails. The GitHub job can finish attaching the
|
|
150
|
+
same artefacts to an existing release. Do not run release builds or publish
|
|
151
|
+
packages locally to work around a failed workflow.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kristof Daja
|
|
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,235 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: zabbix-crontroller
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Monitor cron jobs in Zabbix with crontab as the single source of truth.
|
|
5
|
+
Project-URL: Homepage, https://github.com/theriverman/zabbix-crontroller
|
|
6
|
+
Project-URL: Repository, https://github.com/theriverman/zabbix-crontroller
|
|
7
|
+
Author-email: Kristof Daja <22156894+theriverman@users.noreply.github.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: No Input/Output (Daemon)
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: System :: Monitoring
|
|
19
|
+
Classifier: Topic :: System :: Systems Administration
|
|
20
|
+
Classifier: Topic :: Text Processing
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.14
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Zabbix-cRontroller
|
|
26
|
+
Monitor cron jobs in Zabbix with crontab as the single source of truth
|
|
27
|
+
|
|
28
|
+
# Introduction
|
|
29
|
+
Keep job schedules and metadata in the user's crontab. The `zc` execution wrapper
|
|
30
|
+
records each run's exit status, duration, stdout and stderr in private per-user
|
|
31
|
+
storage. It forwards output to cron or the entry's existing redirections.
|
|
32
|
+
|
|
33
|
+
This implementation provides local run evidence. Zabbix integration and direct
|
|
34
|
+
file, syslog and journal collection are not implemented yet.
|
|
35
|
+
|
|
36
|
+
# Installation & Integration
|
|
37
|
+
|
|
38
|
+
Use Python 3.14 and uv on Linux with cron installed. From a checkout, install for
|
|
39
|
+
the current user and create the short-name symlink:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
UV_TOOL_BIN_DIR="$HOME/.local/bin" uv tool install --python 3.14 .
|
|
43
|
+
ln -s zabbix-crontroller "$HOME/.local/bin/zc"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For a system installation with sudo:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
sudo env UV_TOOL_DIR=/opt/zabbix-crontroller/tools \
|
|
50
|
+
UV_PYTHON_INSTALL_DIR=/opt/zabbix-crontroller/python \
|
|
51
|
+
UV_TOOL_BIN_DIR=/usr/local/bin uv tool install --python 3.14 .
|
|
52
|
+
sudo ln -s zabbix-crontroller /usr/local/bin/zc
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
These are local installations, not release builds. Release packages are produced
|
|
56
|
+
only by tagged GitHub workflows. The executable is `zabbix-crontroller`; `zc`
|
|
57
|
+
must be a symlink to it. Use its absolute path in cron, or explicitly set cron's
|
|
58
|
+
`PATH` to include its directory. A non-root example is
|
|
59
|
+
`PATH=/home/batch/.local/bin:/usr/bin:/bin`; cron does not expand `$HOME` in PATH.
|
|
60
|
+
For system installations, the uv tool environment and interpreter must be readable
|
|
61
|
+
and executable by the job owners; the shared `/opt` locations avoid placing them
|
|
62
|
+
inside root's private home. Validate from each job owner's account.
|
|
63
|
+
|
|
64
|
+
Validate a candidate file, or the current user's installed crontab:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
zc validate example.crontab
|
|
68
|
+
zc validate
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Validation checks the complete syntax and then wrapper and shell availability as
|
|
72
|
+
the invoking user. It never executes a job. Success returns `0`; invalid input,
|
|
73
|
+
acquisition failures and unavailable executables return `1`. Missing crontabs are
|
|
74
|
+
failures, not successful empty configurations.
|
|
75
|
+
|
|
76
|
+
# Syntax
|
|
77
|
+
|
|
78
|
+
Keep schedules and commands in your normal user crontab. The
|
|
79
|
+
[Syntax Specification](docs/SYNTAX_SPECIFICATION.md) defines the frozen `1.0`
|
|
80
|
+
notation supported by the parser.
|
|
81
|
+
|
|
82
|
+
1. Declare `# zc:version = "1.0"` exactly once, before `# zc:start`.
|
|
83
|
+
2. Enclose all monitored jobs in one section, from `# zc:start` to `# zc:end`.
|
|
84
|
+
3. Start each job declaration with `job`: a stable, unique identifier using
|
|
85
|
+
lowercase ASCII letters, digits, underscores or hyphens, beginning with a letter.
|
|
86
|
+
4. Add a required `name` and, if useful, an optional `description`. These can
|
|
87
|
+
contain Unicode; the description may appear before or after the name.
|
|
88
|
+
5. Put exactly one cron entry immediately after its metadata. It completes the
|
|
89
|
+
job declaration. `# zc:end` closes the entire section, not an individual job.
|
|
90
|
+
6. Begin the command with `zc run JOB -- 'COMMAND'`, repeating the exact `job`
|
|
91
|
+
identifier. Use one literally quoted shell command. Both executable names and
|
|
92
|
+
absolute paths ending in `zc` or `zabbix-crontroller` are accepted.
|
|
93
|
+
|
|
94
|
+
For example, a description is not required:
|
|
95
|
+
|
|
96
|
+
```cron
|
|
97
|
+
# zc:version = "1.0"
|
|
98
|
+
PATH=/usr/local/bin:/usr/bin:/bin
|
|
99
|
+
# zc:start
|
|
100
|
+
# zc:job = "backup-postgres"
|
|
101
|
+
# zc:name = "Orders database backup"
|
|
102
|
+
30 1 * * * zc run backup-postgres -- '/opt/company/maintenance/bin/backup-postgres --database orders'
|
|
103
|
+
# zc:end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Write one property per line using a quoted TOML string. Keep each metadata header
|
|
107
|
+
and its cron entry together. Blank lines, ordinary comments and environment
|
|
108
|
+
assignments may appear between completed jobs. Delimiters may have trailing
|
|
109
|
+
`#` comments, but take no value. Changing a name or command does not require
|
|
110
|
+
changing the job identifier.
|
|
111
|
+
|
|
112
|
+
The wrapper executes the quoted command using cron's `SHELL`, or `/bin/sh` when
|
|
113
|
+
unset. Put pipelines, command chains and substitutions inside single quotes so
|
|
114
|
+
they execute within the monitored shell. Keep existing output redirections
|
|
115
|
+
outside the quotes if they should receive forwarded output while capture stays
|
|
116
|
+
separate. Use `'"'"'` to include an apostrophe in a single-quoted command.
|
|
117
|
+
Cron still requires escaping literal percentages as `\%`, even inside quotes.
|
|
118
|
+
See [runtime behaviour and evidence](docs/RUNTIME.md) for limits and failure
|
|
119
|
+
handling.
|
|
120
|
+
|
|
121
|
+
Every cron entry inside the section needs its own metadata. An additional entry
|
|
122
|
+
without metadata is an error, including after the final job. Unannotated entries
|
|
123
|
+
are allowed before and after the section. Job metadata outside the section is
|
|
124
|
+
an error. An empty section is valid, but both delimiters remain required.
|
|
125
|
+
|
|
126
|
+
Commenting out only a job's cron entry is an error: its metadata will not attach
|
|
127
|
+
to a later entry. To stop discovering a job while keeping it running, remove its
|
|
128
|
+
metadata header **and move its cron entry outside the monitored section**.
|
|
129
|
+
Preserve the relevant environment settings when moving it; section boundaries
|
|
130
|
+
do not reset the cron environment. Removing only the header inside the section
|
|
131
|
+
is an error. Remove the entire declaration to remove both monitoring and execution.
|
|
132
|
+
Malformed configurations prevent publication of the discovery snapshot.
|
|
133
|
+
|
|
134
|
+
## Syntax example
|
|
135
|
+
|
|
136
|
+
The [example crontab](example.crontab) contains five monitored jobs and a separate
|
|
137
|
+
unmonitored entry:
|
|
138
|
+
|
|
139
|
+
```cron
|
|
140
|
+
# Production batch jobs — svc_batch@lon-batch-01
|
|
141
|
+
# Owner: Platform Operations
|
|
142
|
+
# Host timezone: Europe/London. Change requests must reference an approved ticket.
|
|
143
|
+
|
|
144
|
+
# zc:version = "1.0"
|
|
145
|
+
|
|
146
|
+
SHELL=/bin/bash
|
|
147
|
+
PATH=/usr/local/bin:/usr/bin:/bin
|
|
148
|
+
MAILTO=platform-operations@example.com
|
|
149
|
+
HOME=/var/lib/batch
|
|
150
|
+
|
|
151
|
+
# zc:start
|
|
152
|
+
|
|
153
|
+
# zc:job = "supplier-sync"
|
|
154
|
+
# zc:name = "Supplier stock synchronisation"
|
|
155
|
+
# zc:description = "Collect supplier stock feeds during business hours, Monday–Friday."
|
|
156
|
+
12 7-19 * * 1-5 zc run supplier-sync -- '/usr/bin/flock -n /var/lib/batch/locks/supplier-sync.lock /opt/company/integrations/bin/supplier-sync --config /etc/company/supplier-sync.toml' >> /var/log/batch/supplier-sync.log 2>&1
|
|
157
|
+
|
|
158
|
+
# zc:job = "backup-postgres"
|
|
159
|
+
# zc:name = "Orders database backup"
|
|
160
|
+
# zc:description = "Nightly database backup; credentials supplied via .pgpass."
|
|
161
|
+
30 1 * * * zc run backup-postgres -- '/opt/company/maintenance/bin/backup-postgres --database orders --retention-days 14' >> /var/log/batch/backup-postgres.log 2>&1
|
|
162
|
+
|
|
163
|
+
# zc:job = "reconcile-payments"
|
|
164
|
+
# zc:name = "Reconcile payments"
|
|
165
|
+
# zc:description = "Generate the previous business day's reconciliation report before Finance arrives."
|
|
166
|
+
15 6 * * 1-5 zc run reconcile-payments -- '/opt/company/finance/bin/reconcile-payments --period previous-business-day' >> /var/log/batch/reconcile-payments.log 2>&1
|
|
167
|
+
|
|
168
|
+
# zc:job = "staging-cleanup"
|
|
169
|
+
# zc:name = "Staging cleanup"
|
|
170
|
+
# zc:description = "Remove expired integration staging files during the Sunday maintenance window."
|
|
171
|
+
45 3 * * 0 zc run staging-cleanup -- '/usr/bin/find /var/lib/batch/staging -xdev -type f -mtime +30 -delete' >> /var/log/batch/staging-cleanup.log 2>&1
|
|
172
|
+
|
|
173
|
+
# zc:job = "sap-export"
|
|
174
|
+
# zc:name = "SAP export"
|
|
175
|
+
# zc:description = "Forward pending warehouse transactions to SAP; prevent overlapping runs."
|
|
176
|
+
*/5 * * * * zc run sap-export -- '/usr/bin/flock -n /var/lib/batch/locks/sap-export.lock /opt/company/integrations/bin/sap-export --env production' >> /var/log/batch/sap-export.log 2>&1
|
|
177
|
+
|
|
178
|
+
# zc:end
|
|
179
|
+
|
|
180
|
+
# This separate cron entry runs outside monitoring.
|
|
181
|
+
45 3 * * 0 /usr/bin/find /var/lib/batch/staging -xdev -type f -mtime +30 -delete >> /var/log/batch/staging-cleanup.log 2>&1
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
# Library usage
|
|
185
|
+
|
|
186
|
+
Import the parser independently of the application. The caller supplies the
|
|
187
|
+
crontab text; the parser does not run `crontab -l`, execute jobs, read files or
|
|
188
|
+
contact Zabbix.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from pathlib import Path
|
|
192
|
+
|
|
193
|
+
from zabbix_crontroller.parser import parse_crontab
|
|
194
|
+
|
|
195
|
+
text = Path("example.crontab").read_text(encoding="utf-8")
|
|
196
|
+
crontab = parse_crontab(text)
|
|
197
|
+
|
|
198
|
+
for job in crontab.jobs:
|
|
199
|
+
print(job.job_id, job.name, job.schedule, job.command)
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`parse_crontab(text: str) -> Crontab` returns the declared `version` and a tuple of
|
|
203
|
+
`CronJob` records in source order. Jobs contain the identifier, name, optional
|
|
204
|
+
description, schedule, complete command, `wrapper_executable`, environment
|
|
205
|
+
snapshot and source line numbers. The wrapper field contains its decoded name
|
|
206
|
+
or absolute path; parsing does not check whether that executable is installed.
|
|
207
|
+
Records and their environment mappings are read-only. Missing or blank
|
|
208
|
+
descriptions become `None`. An explicitly empty monitored section returns an
|
|
209
|
+
empty `jobs` tuple.
|
|
210
|
+
|
|
211
|
+
The parser extracts five-field schedules and `@` nicknames. Schedule field
|
|
212
|
+
values, nickname availability, calendar ranges, time zones and daemon-specific
|
|
213
|
+
extensions are **not validated**. This is a metadata parser, not a replacement
|
|
214
|
+
for the daemon's crontab validator. Input is interpreted as a user crontab, with
|
|
215
|
+
no seconds or username column. Commands retain their internal and trailing
|
|
216
|
+
whitespace, quoting and cron percent sequences without evaluation. Only the
|
|
217
|
+
literal wrapper invocation is validated; the inner shell programme remains
|
|
218
|
+
opaque. Unmonitored commands retain their previous parsing behaviour.
|
|
219
|
+
|
|
220
|
+
Each environment snapshot contains only preceding explicit assignments, in file
|
|
221
|
+
order, with outer quotes removed. It does not add process environment variables
|
|
222
|
+
or cron defaults, perform substitutions, or model daemon-specific overrides.
|
|
223
|
+
|
|
224
|
+
Invalid input raises `ParseError`, with `ConfigurationError` for version errors
|
|
225
|
+
and `CrontabSyntaxError` for other parsing errors. Exceptions expose `message`,
|
|
226
|
+
`line_number`, `job_id`, `declaration_line_number` and `previous_line_number`;
|
|
227
|
+
inapplicable locations are `None`. Source lines are one-based. The exception text
|
|
228
|
+
includes the relevant locations. Parsing returns a complete result or raises an
|
|
229
|
+
error; it never returns partial discovery data. Non-string input raises
|
|
230
|
+
`TypeError`.
|
|
231
|
+
|
|
232
|
+
# Contributing
|
|
233
|
+
|
|
234
|
+
See [CONTRIBUTE.md](CONTRIBUTE.md) for development setup, tests and contribution
|
|
235
|
+
guidelines.
|