pax_global_header 0000666 0000000 0000000 00000000064 14712653334 0014522 g ustar 00root root 0000000 0000000 52 comment=09a2fab3d6c4315b876345d246349dfd4059ed50
unioslo-zabbix-cli-09a2fab/ 0000775 0000000 0000000 00000000000 14712653334 0015722 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/.github/ 0000775 0000000 0000000 00000000000 14712653334 0017262 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/.github/workflows/ 0000775 0000000 0000000 00000000000 14712653334 0021317 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/.github/workflows/build.yml 0000664 0000000 0000000 00000013107 14712653334 0023143 0 ustar 00root root 0000000 0000000 name: build zabbix-cli
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+*'
concurrency:
group: build-zabbix-cli-${{ github.head_ref }}
jobs:
build_pypi:
name: Build wheels and source distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Set up Python 3.12
run: uv python install 3.12
- name: Install build dependencies
run: uv sync --all-extras --dev
- name: Build source distribution
run: uv run python -m build
- uses: actions/upload-artifact@v4
with:
name: pypi_artifacts
path: dist/*
if-no-files-found: error
build_pyinstaller:
name: Build pyinstaller binary
strategy:
matrix:
os:
- ubuntu-latest
- windows-latest
- macos-13 # only non-large x86 macOS runner image available
- macos-latest
include:
- os: ubuntu-latest
platform: linux-x86_64
container: redhat/ubi8:latest
- os: windows-latest
platform: win-x86_64
- os: macos-13
platform: macos-x86_64
- os: macos-latest
platform: macos-arm64
python-version:
- '3.12'
runs-on: ${{ matrix.os }}
container:
image: ${{ matrix.container }}
steps:
- name: Install RHEL 8 dependencies
if: contains(matrix.container, 'redhat/ubi8')
run: dnf install -y git binutils
- name: Ensure git is available
run: git --version
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Set up Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install build dependencies
run: uv sync --all-extras --dev
- name: Build binary with PyInstaller
run: uv run pyinstaller --onefile zabbix_cli/main.py --name zabbix-cli
- name: Set platform binary names
shell: bash
run: |
VERSION="${{ github.ref_name }}"
BASE_NAME="zabbix-cli-${VERSION}-${{ matrix.platform }}"
if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
echo "BINARY_NAME=${BASE_NAME}.exe" >> $GITHUB_ENV
echo "SOURCE_NAME=dist/zabbix-cli.exe" >> $GITHUB_ENV
else
echo "BINARY_NAME=${BASE_NAME}" >> $GITHUB_ENV
echo "SOURCE_NAME=dist/zabbix-cli" >> $GITHUB_ENV
fi
- name: Rename binary
shell: bash
run: mv "${{ env.SOURCE_NAME }}" "dist/${{ env.BINARY_NAME }}"
- uses: actions/upload-artifact@v4
with:
name: ${{ env.BINARY_NAME }}
path: dist/${{ env.BINARY_NAME }}
if-no-files-found: error
publish_pypi:
name: Publish PyPI release
needs:
- build_pypi
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
name: pypi_artifacts
path: dist
- name: Push build artifacts to PyPI
uses: pypa/gh-action-pypi-publish@v1.8.14
publish_github:
name: Publish GitHub release
needs:
- build_pypi
- build_pyinstaller
- publish_pypi
runs-on: ubuntu-latest
steps:
- name: Download PyInstaller binaries
uses: actions/download-artifact@v4
with:
pattern: zabbix-cli-*
path: dist
merge-multiple: true
- name: Download wheel and source distributions
uses: actions/download-artifact@v4
with:
pattern: pypi_artifacts
path: dist
merge-multiple: true
- name: Generate SHA256 checksums
id: sha
run: |
cd dist
echo "checksums<> $GITHUB_OUTPUT
sha256sum * >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
files: dist/*
body: |
Release ${{ github.ref_name }}
## Binary Downloads
Platform | Architecture | Download
---------|--------------|----------
Linux | x86_64 | [zabbix-cli-${{ github.ref_name }}-linux-x86_64](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/zabbix-cli-${{ github.ref_name }}-linux-x86_64)
Windows | x86_64 | [zabbix-cli-${{ github.ref_name }}-win-x86_64.exe](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/zabbix-cli-${{ github.ref_name }}-win-x86_64.exe)
macOS | x86_64 | [zabbix-cli-${{ github.ref_name }}-macos-x86_64](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/zabbix-cli-${{ github.ref_name }}-macos-x86_64)
macOS | ARM64 | [zabbix-cli-${{ github.ref_name }}-macos-arm64](https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/zabbix-cli-${{ github.ref_name }}-macos-arm64)
## PyPI Package
### uv
```bash
uv tool install zabbix-cli-uio==${{ github.ref_name }}
```
### pipx
```bash
pipx install zabbix-cli-uio==${{ github.ref_name }}
```
### pip
```bash
pip install zabbix-cli-uio==${{ github.ref_name }}
```
## SHA256 Checksums
```
${{ steps.sha.outputs.checksums }}
```
draft: false
prerelease: false
unioslo-zabbix-cli-09a2fab/.github/workflows/docs.yml 0000664 0000000 0000000 00000001270 14712653334 0022772 0 ustar 00root root 0000000 0000000 name: build-docs
on:
push:
branches:
- master
- docs-dev
paths:
- "docs/**"
- "mkdocs.yml"
- ".github/workflows/docs.yml"
- "pyproject.toml"
- "zabbix_cli/**"
concurrency:
group: docs-deploy
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Install hatch
run: |
uv pip install --system hatch
- name: Build documentation and publish
run: hatch run docs:mkdocs gh-deploy --force
unioslo-zabbix-cli-09a2fab/.github/workflows/test.yml 0000664 0000000 0000000 00000001345 14712653334 0023024 0 ustar 00root root 0000000 0000000 on:
push:
paths-ignore:
- 'docs/**'
- 'debian/**'
- 'rpm/**'
- 'README.md'
pull_request:
name: CI
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v2
- name: Install dependencies
run: |
uv sync --all-extras --dev
- name: Test
run: uv run pytest -vv tests
unioslo-zabbix-cli-09a2fab/.gitignore 0000664 0000000 0000000 00000000474 14712653334 0017717 0 ustar 00root root 0000000 0000000 *.pyc
# Build files
dist/
build/
*.egg-info/
.coverage
.venv/
venv/
.dccache
.vscode/launch.json
my_tests/
# Hatch Pyapp
pyapp
# Dev commands and directories
zabbix_cli/commands/_dev.py
dev/
# Auto-generated docs files
docs/guide/commands/*
!docs/guide/commands/index.md
docs/data
site/
# Pyinstaller
*.spec
unioslo-zabbix-cli-09a2fab/.pre-commit-config.yaml 0000664 0000000 0000000 00000001453 14712653334 0022206 0 ustar 00root root 0000000 0000000 # See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.0
hooks:
- id: pyupgrade
args: [--py37-plus, --keep-runtime-typing]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.7.0"
hooks:
# Run the linter.
- id: ruff
args: [--fix]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/RobertCraigie/pyright-python
rev: v1.1.386
hooks:
- id: pyright
exclude: ^(tests|scripts|docs)/
unioslo-zabbix-cli-09a2fab/AUTHORS 0000664 0000000 0000000 00000001031 14712653334 0016765 0 ustar 00root root 0000000 0000000 # Based on `git shortlog --all -es` with duplicates removed
Alexandru Tică
Andreas Dobloug
Boris Manojlovic
Carl Morten Boger
Emanuele Borin
Fabian Arrotin
Fabian Stelzer
Florian Tham
Fredrik Larsen
Ganesh Hegde
Herdir
Jarle Bjørgeengen
Jean-Baptiste Denis
Jelmer Vernooij
Kim Rioux-Paradis
Logan V
Marius Bakke
Mathieu Marleix
Michael Gindonis
Mustafa Ocak
Mélissa Bertin
Paal Braathen
Peder Hovdan Andresen
Peet Whittaker
Petter Reinholdtsen
Rafael Martinez Guerrero
Retyunskikh Dmitriy
Steve McDuff
Terje Kvernes
Volker Fröhlich
unioslo-zabbix-cli-09a2fab/CHANGELOG 0000664 0000000 0000000 00000033157 14712653334 0017145 0 ustar 00root root 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.3.0]
### Added
- New configuration file table `[app.output]`:
- `format`: Default output format for commands. Defaults to `"table"`.
- `color`: Enable or disable color output. Defaults to `true`.
- `paging`: Enable or disable paging of output. Defaults to `false`.
- `theme`: Color theme for output. Defaults to `"default"`.
- Application now automatically assigns deprecated config options to their new equivalents internally.
- New command `update_config` to update an outdated configuration file with new options, as well as any currently applied overrides.
- `show_config --secrets ` option for controlling the display mode of sensitive information in the configuration file. Defaults to `mask`.
- New command `update_host` to update basic information about a host.
- New command `show_proxy_hosts` to show hosts monitored by a given proxy.
### Changed
- Custom auth (token) file paths in config now take precedence over the default path if both exist.
- Application now prompts for Zabbix API URL if missing from config.
- Default logging configuration is performed before loading the configuration file. Ensures a default logging configuration is always present.
- Authentication method + source is now logged on successful authentication.
- No longer attempts to add a user to the logging context when logging in with an auth token.
- Require at least one option to be set for `update_*` commands. Previously, these command would state that the resource was updated even if no changes were made.
- Command `add_proxy_to_group` no longer requires a local address and port argument. If not provided, the application attempts to use the proxy's `local_address` and `local_port` fields. If the proxy does not have these fields, the command fails.
### Deprecated
- Config options moved to `[app.output]` table:
- `app.use_colors` → `app.output.color`
- `app.use_paging` → `app.output.paging`
- `app.output_format` → `app.output.format`
## [3.2.0]
### Added
- Configurable error handling modes for bulk mode under `app.bulk_mode`:
- `strict`: Stop on first error.
- `continue`: Continue on command execution error, report at the end.
- `ignore`: Ignore all errors, including command file parsing errors.
- REPL autocompletion for enums and paths.
- Auto completion for `export_configuration --directory` argument.
### Fixed
- Screen flickering on application startup when not authenticating via username and password prompt.
- `define_host_usermacro` not working as expected when using a macro name that already exists elsewhere.
## [3.1.3]
### Fixed
- Empty macro names not throwing an error in macro commands.
- Pyinstaller built binary on certain Linux versions.
## 3.1.2
### Changed
- Reduced source distribution size by excluding unnecessary files.
## 3.1.1
### Added
- Publish to PyPI.
## 3.1.0
### Added
- Plugin support. See the [plugins documentation](https://unioslo.github.io/zabbix-cli/plugins/) for more information.
### Changed
- `--config` now always creates the config file at the given location if it doesn't exist.
- `show_config` now shows the absolute path to the active configuration file.
## 3.0.3
### Added
- `--limit` option for `show_*` commands to limit the number of results shown:
- `show_usermacro_host_list`
- `show_usermacro_template_list`
- `show_maintenance_periods`
### Changed
- `show_host_usermacros` rendering of `automatic` field.
- Now shows a human readable string instead of `0` or `1`.
- Example formatting.
- Hide defaults for required positional arguments.
- `show_dirs` and `init` no longer requires logging in to the Zabbix API or an existing configuration file.
- Log record format:
- No longer includes the process ID.
- Now includes filename, line number and function name.
- Rich markup is no longer included in log messages.
- Accessing the config when it is not loaded now uses the same sample config as `sample_config` instead of raising an exception.
### Fixed
- `show_usermacro_host_list` not showing all hosts with the given macro.
- `show_usermacro_template_list` not showing all templates with the given macro.
- Auth token file using username from config file instead of from prompt.
## 3.0.2
### Added
- `show_hosts`: `--hostgroup` option for filtering by host group names or IDs.
- `show_last_values`: ` Item ID filtering.
- `show_usergroup`: Group ID filtering.
- `show_usergroups`: Group name or ID filtering.
- `show_users`: `--sort` option for sorting results by a field.
- Status messages when fetching data from the Zabbix API in most `show_*` commands.
- `--limit` option for most `show_*` commands to limit the number of results shown.
- Environment variable `ZABBIX_API_TOKEN` for logging in with an API token.
### Fixed
- Markup errors when rendering Zabbix items with keys containing special characters.
- Environment variables not matching V2 names.
- Before: `ZABBIX_CLI_USERNAME`, `ZABBIX_CLI_PASSWORD`
- After: `ZABBIX_USERNAME`, `ZABBIX_PASSWORD`
## 3.0.1
### Changed
- `migrate_config` no longer requires logging in to the Zabbix API.
### Fixed
- `migrate_config` not migrating username to the new `api.username` field in the resulting TOML configuration file.
- `migrate_config` using `legacy_json_format = true` in the resulting TOML configuration file by default.
- Can force the old JSON format with the new `--legacy-json-format` flag.
## 3.0.0
### Added
- New CLI powered by `typer` and `click-repl`
- Shell autocompletion
- TOML configuration file support
- Old configuration format is deprecated.
- Usage examples for most commands.
- **New configuration options:**
- `app.default_format`: Sets the default CLI output format. Defaults to `table`.
- `app.legacy_json_format`: Enables the old JSON output format. Defaults to `false`.
- **New commands:**
- `add_proxy_to_group`: Add a proxy to a proxy group.
- `create_templategroup`: Create a template group.
- `extend_hostgroup`: Add all hosts from a host group to other host group(s) without removing them from the original group.
- `extend_templategroup`: Add all templates from a group to other group(s) without removing them from the original group.
- `init`: Initialize the CLI configuration file.
- `link_template_to_template`: Link template(s) to template(s).
- `move_hosts`: Move all hosts from one host group to another.
- `move_templates`: Move all templates from one group to another.
- `open`: Open a CLI directory in the system's file manager.
- `remove_hostgroup`: Delete a host group.
- `remove_host_interface`: Delete a host interface.
- `remove_proxy_from_group`: Remove a proxy from a proxy group.
- `remove_templategroup`: Delete a template group.
- `show_dirs`: Show directories used by the CLI.
- `show_host_interfaces`: Show interfaces for a host.
- `show_media_types`: Show media types.
- `show_proxies`: Show proxies.
- `show_proxy_groups`: Show proxy groups.
- `show_proxy_group_hosts`: Show hosts in a proxy group.
- `show_templategroup`: Show a single template group.
- `show_templategroups`: Show all template groups.
- `show_user`: Show details for a single user.
- `unlink_template_from_template`: Unlink template(s) from template(s).
- `update_host_interface`: Update a host interface.
- `update_user`: Update a user.
- `update_hostgroup_proxy`: Assign a proxy to all hosts in one or more host groups.
- `update_hostgroup_proxygroup`: Assign a proxy group to all hosts in one or more host groups.
- **New command options:**
- `add_host_to_hostgroup`:
- `--dryrun`: Preview changes without making them.
- `create_host`:
- `--name`: Host name
- Host name still defaults to host DNS name or IP address if not specified.
- `--description`: Host description
- `create_hostgroup`:
- `--rw-groups`: User groups to give RW permissions to the host group.
- `--ro-groups`: User groups to give RO permissions to the host group. Uses groups from config file if not specified.
- `--no-usergroup-permissions`: Do not set user group permissions. Defaults to `false`.
- `create_host_interface`:
- `--snmp...` TODO
- `import_configuration`:
- `--dryrun`: Preview files to import.
- `--delete-missing`: Delete objects not found in the import file(s).
- `link_template_to_host`:
- `--dryrun`: Preview changes without making them.
- `remove_host_from_hostgroup`:
- `--dryrun`: Preview changes without making them.
- `show_host`:
- `--monitored/--unmonitored`: Filter by monitored status
- `--maintenance/--no-maintenance`: Filter by maintenance status
- `--active [available | unavailable | unknown ]`: Filter by active interface availability
- Old positional filter argument syntax is deprecated.
- `show_hosts`:
- `--limit`: Limit number of hosts to show.
- As well as the new `show_host` options.
- `show_hostgroup`:
- `--hosts/--no-hosts`: Show hosts in the group
- `show_hostgroups`:
- `--hosts/--no-hosts`: Show hosts in the group
- `show_trigger_events`
- `--trigger-id`: Trigger ID(s) to get events for.
- Corresponds to old positional argument 1.
- `--host`: Host(s) to get events for.
- `--hostgroup`: Host group(s) to get events for.
- `--limit`: Limit number of events to show
- Corresponds to old positional argument 2.
- Defaults to 10 (was 1).
- `show_usergroup`:
- `--sort`: Sort results by a field.
- `show_usergroups`:
- `--sort`: Sort results by a field.
- `show_usergroup_permissions`:
- `--sort`: Sort results by a field.
- `unlink_template_from_host`:
- `--dryrun`: Preview changes.
- `update_host_proxy`:
- `--dryrun`: Preview changes.
- **New command arguments:**
- `show_templates`:
- `template_names`: Template name(s) to filter by. Shows all templates by default. Supports wildcards.
- `show_hostgroup`:
- `name`: Host group name(s) to filter by. Shows all host groups by default. Supports wildcards.
### Changed
- Commands now take named options instead of positional arguments.
- Positional arguments are deprecated.
- JSON output is no longer always a dict with numeric string keys.
- See V3 migration guide for more information.
- The old format can be enabled with the new option `app.legacy_json_format` in the new TOML configuration file.
- When loading configuration from a legacy `.conf` file, the old format is assumed.
- **TOML configuration file option names:**
- Table [zabbix_api] → [api]
- `zabbix_api_url` → `url`
- `cert_verify` → `verify_ssl`
- Table [zabbix_config] → [app]
- `system_id` → `username`
- `default_directory_exports` → `export_directory`
- `default_export_format` → `export_format`
- `include_timestamp_export_filename` → `export_timestamps`
- `allow_insecure_authfile` → `allow_insecure_auth_file`
- `logging.logging` → `logging.enabled`
- The original names are deprecated and slated for removal in a future version.
- **Configuration file defaults:**
- `app.default_admin_usergroups` defaults to `[]` (empty list)
- `app.default_create_user_usergroups` defaults to `[]` (empty list)
- `app.export_timestamps` defaults to `false`
- Exports are automatically overwritten if the file already exists.
- `app.export_format` defaults to `json` (was `xml`)
- Application now creates a config file on launch if it doesn't exist.
- **Command changes:**
- `create_host_interface`
- Default port number is now determined by interface type.
- Agent: 10050
- SNMP: 161
- IPMI: 623
- JMX: 12345
- `define_host_monitoring_status`:
- Renamed to `monitor_host`.
- `link_template_to_hostgroup`:
- Renamed to `add_template_to_group`.
- `show_host_inventory`
- Now shows shows any inventory field that is set for the host in the table output.
- Includes _all_ inventory fields in JSON output.
- `show_hostgroup_permissions`:
- Shows permissions for all host groups by default.
- `show_proxies`:
- Now takes a `name_or_id` argument to filter by proxy name or ID. Comma-separated. Supports wildcards.
- `show_zabbixcli_config`:
- Renamed to `show_config`.
- `unlink_template_from_hostgroup`:
- Renamed to `remove_template_from_group`.
- No longer unlinks and clears templates from each other.
- This was a bug/misunderstanding of the Zabbix API in the old version.
- Use `unlink_template_from_template` to unlink and clear templates from each other.
- `update_host_proxy`
- Now supports setting proxy for multiple hosts at once using wildcards.
- Output format is changed.
- Now groups hosts by proxy prior to update.
### Deprecated
- `zabbix-cli.conf` format. Prefer the new TOML configuration file format.
- Config file options:
- `zabbix_config.system_id` → `api.username`
- **Commands:**
- `unlink_template_from_hostgroup`:
- Renamed to `remove_template_from_group`.
- `define_host_monitoring_status`:
- Renamed to `monitor_host`
- `show_zabbixcli_config`:
- Renamed to `show_config`
- `zabbix-cli-init` script.
- Replaced by `zabbix-cli init` command.
- `zabbix-cli-bulk-execution` script.
- Replaced by `zabbix-cli --file`.
### Removed
- Support for Zabbix <1.8 login using `user.authenticate`.
### Internal
- Use Hatch for building and publishing.
- Switch from setup.py to pyproject.toml.
- Add `pre-commit` hooks.
- Add `pytest` tests
- Use Ruff for linting and formatting.
- Use Pyright for static type checking.
- API code rewritten with Pydantic data models.
unioslo-zabbix-cli-09a2fab/LICENSE 0000664 0000000 0000000 00000104461 14712653334 0016735 0 ustar 00root root 0000000 0000000 GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
{project} Copyright (C) {year} {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
unioslo-zabbix-cli-09a2fab/README.md 0000664 0000000 0000000 00000025351 14712653334 0017207 0 ustar 00root root 0000000 0000000 # Zabbix-cli
[](https://pypi.org/project/zabbix-cli-uio/)
[]()
[]()

**Zabbix-CLI v3 has been completely rewritten from the ground up. The old version can be found [here](https://github.com/unioslo/zabbix-cli/tree/2.3.2).**
## About
Zabbix-cli is a command line interface for performing common administrative tasks tasks in [Zabbix monitoring system](https://www.zabbix.com/) via the [Zabbix API](https://www.zabbix.com/documentation/current/en/manual/api).
The zabbix-cli code is written in [Python](https://www.python.org/) and distributed under the GNU General Public License v3. It has been developed and tested by [University Center for Information Technology](https://www.usit.uio.no/) at [the University of Oslo, Norway](https://www.uio.no/).
The project home page is on [GitHub](https://github.com/unioslo/zabbix-cli). Please report any issues or improvements there.
The manual is available online at .
## Install
### From source
> [!NOTE]
> We are in the process of acquiring the name `zabbix-cli` on PyPI. Until then, installation must be done via the mirror package `zabbix-cli-uio`.
#### [uv](https://docs.astral.sh/uv/getting-started/installation/)
```bash
uv tool install zabbix-cli-uio
```
#### [uvx](https://docs.astral.sh/uv/#tool-management)
```bash
uvx --from zabbix-cli-uio zabbix-cli
```
#### [pipx](https://pipx.pypa.io/stable/)
```bash
pipx install zabbix-cli-uio
```
### Homebrew
A homebrew package exists, but it is maintained by a third party. It can be installed with:
```bash
brew install zabbix-cli
```
### Binary
Binaries built with PyInstaller can be found on the [releases page](https://github.com/unioslo/zabbix-cli/releases). We build binaries for Linux (x86), macOS (ARM & x86) and Windows (x86) for each release.
## Quick start
Running `zabbix-cli` for the first time will prompt for a Zabbix URL, username and password. The URL should be the URL of the Zabbix web server without the `/api_jsonrpc.php` path.
Running without arguments will start the REPL:
```bash
zabbix-cli
```
## Usage
Zabbix-cli is a command line interface for Zabbix. It can be used in three ways:
1. **Interactive mode**: Start the REPL by running `zabbix-cli`. This will start a shell where you can run multiple commands in a persistent session.
2. **Single command**: Run a single command by running `zabbix-cli COMMAND`. This will run the command and print the output.
3. **Batch mode**: Run multiple commands from a file by running `zabbix-cli -f FILE`. The file should contain one command per line.
Command reference can be found in the [online user guide](https://unioslo.github.io/zabbix-cli/guide/commands/) or by running `zabbix-cli --help`.
### Authentication
By default, the application will prompt for a username and password. Once authenticated, the application stores the session token in a file for future use.
For more information about the various authentication methods, see the [authentication guide](https://unioslo.github.io/zabbix-cli/guide/authentication/).
### Configuration
Zabbix-cli needs a config file. It is created when the application is started for the first time. The config file can be created manually with the `init` command:
```bash
zabbix-cli init --zabbix-url https://zabbix.example.com/
```
For more detailed information about the configuration file, see the [configuration guide](https://unioslo.github.io/zabbix-cli/guide/configuration/).
### Formats
Zabbix-cli supports two output formats: table and JSON. The default format is table, but it can be changed with the `--format` parameter:
```bash
# Show hosts in table format (default)
zabbix-cli show_hosts
# Show hosts in JSON format
zabbix-cli --format json show_hosts
# Set format in REPL mode
> --format json show_hosts
```
The default format can be configured with the `app.output.format` config option:
```toml
[app.output]
format = "json"
```
#### Table
The default rendering mode is a [Rich](https://github.com/Textualize/rich) table that adapts to the width of the terminal.
#### JSON
The JSON output format is always in this format, where `ResultT` is the expected result type:
```json
{
"message": "",
"errors": [],
"return_code": "Done",
"result": ResultT
}
```
The type of the `result` field varies based on the command run. For `show_host` it is a single Host object, while for `show_hosts` it is an _array_ of Host objects.
show_host foo.example.com*
```json
{
"message": "",
"errors": [],
"return_code": "Done",
"result": {
"hostid": "10648",
"host": "foo.example.com",
"description": "",
"groups": [
{
"groupid": "22",
"name": "All-hosts",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "46",
"name": "Source-foosource",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "47",
"name": "Hostgroup-bob-hosts",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "48",
"name": "Importance-X",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "49",
"name": "Hostgroup-alice-hosts",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
}
],
"templates": [],
"inventory": {},
"monitored_by": "proxy",
"proxyid": "2",
"proxy_groupid": "0",
"maintenance_status": "0",
"active_available": "0",
"status": "0",
"macros": [],
"interfaces": [
{
"type": 1,
"ip": "",
"dns": "foo.example.com",
"port": "10050",
"useip": 0,
"main": 1,
"interfaceid": "49",
"available": 0,
"hostid": "10648",
"bulk": null,
"connection_mode": "Dns",
"type_str": "Agent"
}
],
"proxy": {
"proxyid": "2",
"name": "proxy-prod02.example.com",
"hosts": [],
"status": null,
"operating_mode": 0,
"address": "127.0.0.1",
"proxy_groupid": "1",
"compatibility": 0,
"version": 0,
"local_address": "192.168.0.1",
"local_port": "10051",
"mode": "Active",
"compatibility_str": "Undefined"
},
"zabbix_agent": "Unknown"
}
}
```
show_hosts foo.*
```json
{
"message": "",
"errors": [],
"return_code": "Done",
"result": [
{
"hostid": "10648",
"host": "foo.example.com",
"description": "",
"groups": [
{
"groupid": "22",
"name": "All-hosts",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "46",
"name": "Source-foosource",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "47",
"name": "Hostgroup-bob-hosts",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "48",
"name": "Importance-X",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
},
{
"groupid": "49",
"name": "Hostgroup-alice-hosts",
"hosts": [],
"flags": 0,
"internal": null,
"templates": []
}
],
"templates": [],
"inventory": {},
"monitored_by": "proxy",
"proxyid": "2",
"proxy_groupid": "0",
"maintenance_status": "0",
"active_available": "0",
"status": "0",
"macros": [],
"interfaces": [],
"proxy": {
"proxyid": "2",
"name": "proxy-prod02.example.com",
"hosts": [],
"status": null,
"operating_mode": 0,
"address": "127.0.0.1",
"proxy_groupid": "1",
"compatibility": 0,
"version": 0,
"local_address": "192.168.0.1",
"local_port": "10051",
"mode": "Active",
"compatibility_str": "Undefined"
},
"zabbix_agent": "Unknown"
}
]
}
```
## Development
Zabbix-cli currently uses [Hatch](https://hatch.pypa.io/latest/) for project management and packaging. To start off, clone the repository:
```bash
git clone https://github.com/unioslo/zabbix-cli.git
```
Then make a virtual environment using Hatch:
```bash
hatch shell
```
This will create a new virtual environment, install the required dependencies and enter the environment.
If you do not wish to use Hatch, you can create a virtual environment manually:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -U -e ".[test]"
```
### Testing
Run unit tests (without coverage):
```bash
hatch run test
```
Generate coverage report:
```bash
hatch run cov
```
### Documentation
To serve the documentation locally:
```bash
hatch run docs:serve
```
This will start a local web server on `http://localhost:8001` that is automatically refreshed when you make changes to the documentation. However, some hooks are only run on startup, such as the creation of pages for each command. Changes to command examples or docstrings will require a restart.
unioslo-zabbix-cli-09a2fab/TODO 0000664 0000000 0000000 00000026175 14712653334 0016425 0 ustar 00root root 0000000 0000000 * Configure file logging for scripts as well???
# IMPORTANT
* add_host_to_hostgroup & remove_host_from_hostgroup should probably take 2 positional arguments as opposed to 2 options. The current implementation is over-complicated.
* Auto relogin + custom error when this happens:
```
✗ ERROR: Failed to get all host groups: ("Error -32602: Invalid params.: Session terminated, re-login, please. while sending {'jsonrpc': '2.0', 'method': 'hostgroup.get', 'params': {'output': 'extend', 'selectHosts': 'extend', 'sortorder': 'ASC', 'sortfield': 'name'}, 'id': 3, 'auth': '1234abc'}", -32602)
```
THIS WILL NOT BE CAUGHT WHEN A METHOD RE-RAISES ZabbixAPICallError for some reason!
* Add type checking method for return value of API calls. E.g. `update` and `massupdate` should return a dict with a key called something like `hostids` containing a list of updated hosts. We should have a method that can be called something like this:
```python
ret = self.host.massupdate(...)
updated = check_return(ret, "hostids")
reveal_type(updated) # List[str]
```
----------------
## API
* Add some sort of `process_params` method that can be used to process params and add them to the ParamsType mapping, to reduce code duplication within each method.
* Add support for both names and IDs in certain API methods. This adds a bunch of complexity, but is required to support some V2 features. Drop them?
* Move all APIStrEnum classes into pyzabbix. Add `enums` module to pyzabbix?
* Wrap most POST/UPDATE/DELETEs in a try/except block that catches ZabbixAPIExceptions and re-raises it with a more understandable error message.
* Some methods already do this, but not all.
* Wrap GETs in try/except as well. About half of the methods do this... Very inconsistent.
* Some sort of type checking of API results.
* If we expect a list, we should have a function that does the appropriate isinstance checks, and possibly also converts each item in the list to the correct type.
* Similarly, each response is likely a dict of some sort, but we should ensure it actually is a dict before we try to access it.
* Log all API calls and responses.
* Successful calls should be logged at INFO level.
* I.e. `logger.info("Acknowledged event: %s. Closed: %s", event_id, close)`
* Failures that raise exceptions don't need to log anything (they are logged by the exception handler).
* In debug mode, we can also log the request and response.
* This will help us debug issues with the API.
### Utils
Use APIStr enums in functions that take in a code and return a string representation. That way we don't create duplicate definitions for the same objects/concepts/choices.
## Args
* Hide positional args from help?
* Consistent hostname vs hostname_or_id arguments
## Auth
* Add configurable auth token file location. Requires a minor refactoring of functions in `auth.py` to locate the custom file. Also might not be possible to automatically clear the file when we encounter an error with an exipred token.
## Bulk / -C mode
* Add timeout to commands when running in bulk mode in case command prompts for input.
* Populate cache when in bulk mode.
* Automatically enable headless mode when running in bulk mode.
## Cache
* Implement caching (do we need it?)
* If implememented:
* Add option to enable/disable cache.
* Both in REPL mode and in bulk mode.
* Add command to clear cache.
## Commands
* Add command aliases:
* Two options:
* Add new name `show_host` -> `host-show`, hide old name. (easy)
* Need some sort of decorator like @app.old_name("show_host")
* Registers the command with hidden=True
* Adds `Old name: show_host` to the bottom of the help text.
* This lets us traverse all the commands and create a mapping of old to new names, as well as providing it in the help text.
* Add new sub-apps a la Harbor CLI: `show_host` -> `host show` (hard)
* Requires a bigger refactoring and is semantically different from the current set up.
* Sort command categories by help panel names.
* Sorting order seems to be alphabetical BUT BASED ON THE COMMAND NAMES. So if one category has a command that starts with "A" and another category has a command that starts with "B", then the category with the "A" command will be listed first, regardless of the category name. Why...
* `show_template` and `show_templates`:
* Remove one of the commands and set the default to `*` to the other command, so we mimick the old behavior. Since we can filter names with both of them, the only difference is whether we show all results or just one (meaningless distinction).
* `show_alarms`
* Color severity levels.
* `remove_template_from_group`:
* Add `--dryrun` option.
## Config
* Catch when users use deprecated names and warn them. Also warn when we load a .conf config.
* Add support for .fixed.conf files
* Add loading config from multiple sources. Each new config adds to the existing config and overrides values, like how it was in v2.
* Use `ctx.config_map` to set command param defaults from config.
* Lets us set defaults for all commands in one place.
*
*
## Documentation
* Convert all docstrings to google docstring style.
### Examples
* Add examples to all commands.
* Render examples in documentation. Extract rich-formatted text and render it as markdown.
* Append `zabbix-cli` to the beginning of each example (we omit this in the app since the REPL is the primary use case). Should we?
* Define examples in own mapping in a separate module. This module is not imported on runtime and is only used when --help is invoked. The command looks up its own examples based on name. I.e.
```python
EXAMPLES = {
"show_host": [
Example(
"Show a host by name",
"show_host foo.example.com"
),
Example(
"Show first host with a name that starts with 'foo'",
"show_host 'foo*'"
)
]
}
```
This way we can have an arbitrary number of examples without impacting startup time. Need to override the the way the help is looked up to make this work.
## Exceptions
### Zabbix API Exceptions
* Store error code and message in ZabbixAPIException, so that we can automatically add it to the error message when we write custom ZabbixAPIExceptions.
* **VERY VERY IMPORTANT:** If we raise ZabbixAPIException from a ZabbixAPIException, we should be able to extract the data from the original exception (`__cause__`) and add it to whatever we are printing.
* Don't log traceback of "expected" errors such as `ZabbixNotFound`.
* Perhaps add some sort of base class we handle for this such as `ZabbixStatusError`?
* Handle connection errors more gracefully. Example: if we try to connect to UIO Zabbix without SOCKS proxy, we get a `HTTPStatusError` with a 403 status code. We should catch this and raise a more meaningful error.
```
HTTPStatusError: Client error '403 Forbidden' for url 'https://example.com/api_jsonrpc.php'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403
```
### Pydantic Exceptions
* Log all data and API version whenever we get a validation error.
* This will help us debug issues with the API.
* We should also log the full request and response.
* Add a `--debug` flag that enables debug logging???
## Hosts
* Go through all `get_hosts()` calls. Make sure we only search for one term (why?).
* Show templates and proxies in `show_host` and `show_hosts`
## Logging
* Document that setting `log_file = ""` causes logs to be sent to stderr.
* This is not recommended, but useful for real-time debugging.
* Maybe toggle normal console messages when this is set?
* Either only use root logger OR add a logger for each module.
* Currently we have a mix of both.
* Log record markup normalizer. Remove Rich formattings from log records.
## Prompts
* Remove most prompts
* Huge offenders:
* `_handle_hostgroup_args`
* `_handle_templategroup_args`
* `_handle_template_arg`
* Turn some text prompts into arrow key selection prompts:
* `create_notification_user`: `mediatype`
* Fetch all media types then display them in a list.
## Rendering
* [ ] Turn `ColsRowsType` into some sort of BaseModel type that validates that row length matches cols length.
* Rewrite `TableRenderable.__cols_rows__` to use this new type.
* **Performance implications?**
* Add some sort of converting of iterable values to newline separated strings in `__rows__`
* Prevent "no results found" from being printed when we are trying to render the result of a create/modify action that created/modified 0 objects.
* Example: `add_host_to_hostgroup` when host is already in the host group.
## Legacy
* Assume legacy JSON format when loading from .conf file.
* Render some names as legacy names when using legacy JSON format.
* E.g. `name` instead of `macro` for `show_host_usermacros`
## Serialization
* Always serialize string representations of codes (`Maintenance (0)`)
* We do not strive for 1:1 compatibility with the Zabbix API. It's more important that users can read the output.
* We need to fix `Host` to convert to string representations instead of codes.
## scripts
* Some sort of shared callback function between all scripts and the main CLI. This way we can pass in config etc, and have a common way of handling errors etc.
## Templates
* Remove `--strict` option. Adds complexity without much extra value. Users can use `--dryrun` to ensure that the command will work as expected.
## Tests
* Choice enum mappings contain all choices.
* I.e. `InterfaceConnectionMode` & `InterfaceType`
--------------------
## Repo
### Run pyupgrade on all files not referenced by Typer
We only need runtime typing on the functions that typer use for type inference. Modules such as Pyzabbix can be upgraded to use modern typing features such as built-in generics (3.9) and | (3.10).
--------------------
### Imports
* [x] Reduce number of cross-imports
* [x] Perform more inline imports
* [x] Define config model in separate file that is not imported on startup.
* [x] Profile using py-spy. Preliminary tests show that the majority of the slowdowns are from defining Pydantic models on runtime.
--------------------
## New command ideas
### Assign unproxied hosts to proxies: `assign_host_to_proxy`
* Allow for pattern matching in host names.
* E.g. `assign_host_to_proxy "proxy-prod01.example.com" "*.example.com"`
### Assign all hosts in host group to proxy: `assign_hostgroup_to_proxy`
* Allow for pattern matching in host names.
* E.g. `assign_hostgroup_to_proxy "proxy-prod01.example.com" "Siteadmin-iti-*"`
### Show help for a command or category: `help`
Usage:
* `zabbix-cli help host`
* `zabbix-cli help show_host`
### Details about a specific host interface: `show_host_interface`
Usage:
* `zabbix-cli show_host_interface `
* Shows more details about the interface than `show_host_interfaces`
### Set a host interface as default (unsetting other as default): `set_default_host_interface`
Basically, when we create a new host interface of a type we already have default for, we cannot unset the old default. This command allows us to do that.
### Create template: `create_template`
....
unioslo-zabbix-cli-09a2fab/bin/ 0000775 0000000 0000000 00000000000 14712653334 0016472 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/bin/zabbix-cli 0000775 0000000 0000000 00000001050 14712653334 0020440 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3.12
"""Run script for pyinstaller.
Create a standalone executable as:
pyinstaller --name zabbix-cli bin/zabbix-cli -F --hidden-import=zabbix_cli.app
The finished binary will be in the `dist` directory.
The hidden import is required to include the host submodules in the executable,
due to using dynamic imports.
"""
from __future__ import annotations
import re
import sys
from zabbix_cli.main import main
if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main())
unioslo-zabbix-cli-09a2fab/bin/zabbix-cli-bulk-execution 0000775 0000000 0000000 00000001071 14712653334 0023377 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3.12
"""Run script for pyinstaller.
Create a standalone executable as:
pyinstaller --name zabbix-cli-bulk-execution bin/zabbix-cli-bulk-execution -F
The finished binary will be in the `dist` directory.
The hidden import is required to include the host submodules in the executable,
due to using dynamic imports.
"""
from __future__ import annotations
import re
import sys
from zabbix_cli.scripts.bulk_execution import main
if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main())
unioslo-zabbix-cli-09a2fab/bin/zabbix-cli-init 0000775 0000000 0000000 00000001033 14712653334 0021402 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3.12
"""Run script for pyinstaller.
Create a standalone executable as:
pyinstaller --name zabbix-cli-init bin/zabbix-cli-init -F
The finished binary will be in the `dist` directory.
The hidden import is required to include the host submodules in the executable,
due to using dynamic imports.
"""
from __future__ import annotations
import re
import sys
from zabbix_cli.scripts.init import main
if __name__ == "__main__":
sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0])
sys.exit(main())
unioslo-zabbix-cli-09a2fab/docs/ 0000775 0000000 0000000 00000000000 14712653334 0016652 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/.includes/ 0000775 0000000 0000000 00000000000 14712653334 0020536 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/.includes/admonition-pypi.md 0000664 0000000 0000000 00000000251 14712653334 0024176 0 ustar 00root root 0000000 0000000 !!! note "PyPI package name"
We are in the process of acquiring the PyPI project `zabbix-cli`. Until then, installation must be done via the alias `zabbix-cli-uio`.
unioslo-zabbix-cli-09a2fab/docs/.includes/config-locations.md 0000664 0000000 0000000 00000000733 14712653334 0024321 0 ustar 00root root 0000000 0000000 === "Linux"
- `./zabbix-cli/zabbix-cli.toml`
- `$XDG_CONFIG_HOME/zabbix-cli/zabbix-cli.toml`
- `$XDG_CONFIG_DIRS/zabbix-cli/zabbix-cli.toml`
=== "macOS"
- `./zabbix-cli/zabbix-cli.toml`
- `~/Library/Application Support/zabbix-cli/zabbix-cli.toml`
- `~/Library/Preferences/zabbix-cli/zabbix-cli.toml`
=== "Windows"
- `.\zabbix-cli\zabbix-cli.toml`
- `%LOCALAPPDATA%\zabbix-cli\zabbix-cli.toml`
- `%APPDATA%\zabbix-cli\zabbix-cli.toml`
unioslo-zabbix-cli-09a2fab/docs/.includes/pipx-multiple.md 0000664 0000000 0000000 00000001035 14712653334 0023670 0 ustar 00root root 0000000 0000000 pipx supports installing multiple versions of the same package by giving each installation a custom suffix. For example, if we have an existing installation of Zabbix CLI, and we wish to install a newer version of Zabbix CLI without shadowing or overwriting the existing installation, we can do so:
```bash
pipx install zabbix-cli>=3.0.0 --suffix @v3
```
This installs Zabbix CLI >= 3.0.0 with the suffix `@v3`, and we can run it with:
```bash
zabbix-cli@v3
```
and the existing installation can be run as usual:
```bash
zabbix-cli
```
unioslo-zabbix-cli-09a2fab/docs/.includes/quick-install.md 0000664 0000000 0000000 00000002524 14712653334 0023643 0 ustar 00root root 0000000 0000000 === "uv"
Install with [`uv`](https://docs.astral.sh/uv/getting-started/installation/) to avoid conflicts with other Python packages in your system:
```bash
uv tool install zabbix-cli-uio
```
To try out Zabbix-CLI without installing it, run it directly with [`uvx`](https://docs.astral.sh/uv/#tool-management):
```bash
uvx --from zabbix-cli-uio zabbix-cli
```
{% include-markdown ".includes/admonition-pypi.md" %}
=== "pipx"
Install with [`pipx`](https://pipx.pypa.io/stable/) to avoid conflicts with other Python packages in your system:
```bash
pipx install zabbix-cli-uio
```
{% include-markdown ".includes/admonition-pypi.md" %}
=== "Homebrew"
You can install `zabbix-cli` with Homebrew:
```bash
brew install zabbix-cli
```
!!! warning
The Homebrew package is maintained by a third party. It may be outdated or contain bugs. For the most up to date version, follow the installation instructions for pipx.
=== "Binary"
Binaries are built with PyInstaller for each release and can be downloaded from the [GitHub releases page](https://github.com/unioslo/zabbix-cli/releases). Download the correct binary for your platform and save it as `zabbix-cli`.
!!! warning "Linux & macOS"
Remember to make the binary executable with `chmod +x zabbix-cli`.
unioslo-zabbix-cli-09a2fab/docs/.includes/upgrade.md 0000664 0000000 0000000 00000001542 14712653334 0022511 0 ustar 00root root 0000000 0000000 === "uv"
```bash
uv tool upgrade zabbix-cli-uio
```
=== "pipx"
```bash
pipx upgrade zabbix-cli-uio
```
=== "Homebrew"
```bash
brew upgrade zabbix-cli
```
=== "Binary (Automatic)"
Zabbix-cli has experimental support for updating itself. You can use the `zabbix-cli update` command to update the application to the latest version.
!!! danger "Write access required"
The application must have write access to itself and the directory it resides in.
```bash
zabbix-cli update
```
=== "Binary (Manual)"
The latest binary can be downloaded from [GitHub releases page](https://github.com/unioslo/zabbix-cli/releases). Download the binary for your platform and replace the current one.
!!! warning "Linux & macOS"
Remember to make the binary executable with `chmod +x zabbix-cli`.
unioslo-zabbix-cli-09a2fab/docs/changelog.md 0000664 0000000 0000000 00000000240 14712653334 0021117 0 ustar 00root root 0000000 0000000 # Changelog
This page documents all the changes to released versions of Zabbix CLI.
{%
include-markdown "../CHANGELOG"
start=""
%}
unioslo-zabbix-cli-09a2fab/docs/guide/ 0000775 0000000 0000000 00000000000 14712653334 0017747 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/guide/authentication.md 0000664 0000000 0000000 00000006001 14712653334 0023305 0 ustar 00root root 0000000 0000000 # Authentication
Zabbix-cli provides several ways to authenticate. They are tried in the following order:
1. [Token - Config file](#api-token-config-file)
1. [Token - Environment variables](#api-token-environment-variables)
1. [Token - Auth token file](#auth-token-file)
1. [Password - Config file](#config-file_1)
1. [Password - Auth file](#auth-file)
1. [Password - Environment variables](#environment-variables_1)
1. [Password - Prompt](#prompt)
## Token
The application supports authenticating with an API or session token. API tokens are created in the Zabbix frontend or via `zabbix-cli create_token`. A session token is obtained by logging in to the Zabbix API with a username and password.
!!! info "Session vs API token"
Semantically, a session token and API token are the same thing from an API authentication perspective. They are both sent as the `auth` parameter in the Zabbix API requests.
### Config file
The token can be set directly in the config file:
```toml
[api]
auth_token = "API_TOKEN"
```
### Environment variables
The API token can be set as an environment variable:
```bash
export ZABBIX_API_TOKEN="API TOKEN"
```
### Auth token file
The application can store and reuse session tokens between runs. This feature is enabled by default and configurable via the following options:
```toml
[app]
# Enable token file storage (default: true)
use_auth_token_file = true
# Customize token file location (optional)
auth_token_file = "/path/to/auth/token/file"
# Enforce secure file permissions (default: true, no effect on Windows)
allow_insecure_auth_file = false
```
**How it works:**
- Log in once with username and password
- Token is automatically saved to the file
- Subsequent runs will use the saved token for authentication
When `allow_insecure_auth_file` is set to `false`, the application will attempt to set `600` (read/write for owner only) permissions on the token file when creating/updating it.
## Username and Password
The application supports authenticating with a username and password. The password can be set in the config file, an auth file, as environment variables, or prompted for when starting the application.
### Config file
The password can be set directly in the config file:
```toml
[api]
username = "Admin"
password = "zabbix"
```
### Auth file
A file named `.zabbix-cli_auth` can be created in the user's home directory or in the application's data directory. The file should contain a single line of text in the format `USERNAME::PASSWORD`.
```bash
echo "Admin::zabbix" > ~/.zabbix-cli_auth
```
The location of the auth file file can be changed in the config file:
```toml
[app]
auth_file = "~/.zabbix-cli_auth"
```
### Environment variables
The username and password can be set as environment variables:
```bash
export ZABBIX_USERNAME="Admin"
export ZABBIX_PASSWORD="zabbix"
```
### Prompt
When all other authentication methods fail, the application will prompt for a username and password. The default username in the prompt can be configured:
```toml
[api]
username = "Admin"
```
unioslo-zabbix-cli-09a2fab/docs/guide/bulk.md 0000664 0000000 0000000 00000002613 14712653334 0021230 0 ustar 00root root 0000000 0000000 # Bulk Operations
Zabbix-CLI supports performing bulk operations with the `--file` option:
```bash
zabbix-cli --file /path/to/commands.txt
```
The `--file` option takes in a file containing commands to run in bulk. Each line in the file should be a separate command. Comments are added by prepending a `#` to the line.
```bash
# /path/to/commands.txt
# This is a comment
show_hostgroup "Linux servers"
create_host foobarbaz.example.com --hostgroup "Linux servers,Applications" --proxy .+ --status on --no-default-hostgroup --description "Added in bulk mode"
show_host foobarbaz.example.com
create_hostgroup "My new group"
add_host_to_hostgroup foobarbaz.example.com "My new group"
remove_host_from_hostgroup foobarbaz.example.com "My new group"
remove_hostgroup "My new group"
remove_host foobarbaz.example.com
```
*Example of a bulk operation file that adds a host and a host group, then removes them.*
## Errors
By default, all errors are fatal. If a command fails, the bulk operation is aborted. This behavior can be changed with the `app.bulk_mode` setting in the configuration file:
```toml
[app]
bulk_mode = "strict" # strict|continue|skip
```
- `strict`: The operation will stop at the first encountered error.
- `continue`: The operation will continue on errors and report them afterwards.
- `skip`: Same as continue, but invalid lines in the bulk file are also skipped. Errors are completely ignored.
unioslo-zabbix-cli-09a2fab/docs/guide/commands/ 0000775 0000000 0000000 00000000000 14712653334 0021550 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/guide/commands/index.md 0000664 0000000 0000000 00000000263 14712653334 0023202 0 ustar 00root root 0000000 0000000 # Commands
Each command and its arguments and/or options are documented in the following pages. The commands are grouped by category.
unioslo-zabbix-cli-09a2fab/docs/guide/configuration.md 0000664 0000000 0000000 00000023527 14712653334 0023151 0 ustar 00root root 0000000 0000000 # Configuration
!!! note "Configuration file directory"
The application uses the [platformdirs](https://pypi.org/project/platformdirs/) package to determine the configuration directory.
The application is configured with a TOML file. The file is created on startup if it doesn't exist.
The configuration file is searched for in the following locations:
{% include ".includes/config-locations.md" %}
## Create a config
The configuration file is automatically created when the application is started for the first time.
The config file can also manually be created with the `init` command:
```bash
zabbix-cli init
```
The application will print the location of the created configuration file.
To bootstrap the config with a URL and username, use the options `--url` and `--user`:
```bash
zabbix-cli init --url https://zabbix.example.com --user Admin
```
To overwrite an existing configuration file, use the `--overwrite` option:
```
zabbix-cli init --overwrite
```
## Config directory
The default configuration directory can be opened in the system's file manager with the `open` command:
```bash
zabbix-cli open config
```
To print the path instead of opening it, use the `--path` option:
```bash
zabbix-cli open config --path
```
## Show config
The contents of the current configuration file can be displayed with `show_config`:
```bash
zabbix-cli show_config
```
## Sample config
A sample configuration file can be printed to the terminal with the `sample_config` command. This can be redirected to a file to create a configuration file in an arbitrary location:
```
zabbix-cli sample_config > /path/to/config.toml
```
A more convoluted way of creating a default config file in the default location would be:
```
zabbix-cli sample_config > "$(zabbix-cli open --path config)/zabbix-cli.toml"
```
The created config looks like this:
```toml
{% include "data/sample_config.toml" %}
```
## Options
=== "`api`"
The `api` section configures the application's Zabbix API connection.
```toml
[api]
url = "https://zabbix.example.com"
username = "Admin"
password = ""
auth_token = ""
verify_ssl = true
```
#### `url`
URL of the Zabbix API host. Should not include the `/api_jsonrpc.php` path.
Type: `str`
```toml
[api]
url = "https://zabbix.example.com"
```
----
#### `username`
Username for Zabbix API authentication. Can be used in combination with `password`, or to provide a default username for the login prompt.
Type: `str`
Default: `Admin`
```toml
[api]
username = "Admin"
```
----
#### `password`
Password to use in combination with a username.
Type: `str`
```toml
[api]
password = "password123"
```
----
#### `auth_token`
Session token or API token to use for authentication. Takes precedence over `username` and `password` if set.
Type: `str`
```toml
[api]
auth_token = "API_TOKEN_123"
```
----
#### `verify_ssl`
Whether to verify SSL certificates.
Type: `bool`
Default: `true`
```toml
[api]
verify_ssl = true
```
=== "`app`"
The `app` section configures general application settings, such as defaults for Zabbix host and group creation, export configuration, and more.
```toml
[app]
default_hostgroups = [
"All-hosts",
]
default_admin_usergroups = []
default_create_user_usergroups = []
default_notification_users_usergroups = [
"All-notification-users",
]
export_directory = "/path/to/exports"
export_format = "json"
export_timestamps = true
use_auth_token_file = true
auth_token_file = "/path/to/auth_token_file"
auth_file = "/path/to/auth_token_file"
history = true
history_file = "/path/to/history_file.history"
bulk_mode = "strict"
allow_insecure_auth_file = true
legacy_json_format = false
```
----
#### `default_hostgroups`
Default host groups to assign to hosts created with `create_host`. Hosts are always added to these groups unless `--no-default-hostgroup` is provided.
Type: `List[str]`
Default: `["All-hosts"]`
```toml
[app]
default_hostgroups = ["All-hosts"]
```
----
#### `default_admin_usergroups`
Default user groups to give read/write permissions to groups created with `create_hostgroup` and `create_templategroup` when `--rw-groups` option is not provided.
Type: `List[str]`
Default: `[]`
```toml
[app]
default_admin_usergroups = ["All-admins"]
```
----
#### `default_create_user_usergroups`
Default user groups to add users created with `create_user` to when `--usergroups` is not provided.
Type: `List[str]`
Default: `[]`
```toml
[app]
default_create_user_usergroups = ["All-users"]
```
----
#### `default_notification_users_usergroups`
Default user groups to add notification users created with `create_notification_user` to when `--usergroups` is not provided.
Type: `List[str]`
Default: `["All-notification-users"]`
```toml
[app]
default_create_user_usergroups = ["All-notification-users"]
```
----
#### `export_directory`
Directory for exports.
Type: `str`
Default: `"/zabbix-cli/exports"`
```toml
[app]
default_create_user_usergroups = "/path/to/exports"
```
----
#### `export_format`
Format for exports.
Type: `str`
Default: `"json"`
```toml
[app]
export_format = "json"
```
----
#### `export_timestamps`
Whether to include timestamps in export filenames.
Type: `bool`
Default: `false`
```toml
[app]
export_timestamps = false
```
----
#### `use_auth_token_file`
Whether to use an auth token file to save session token once authenticated. Allows for reusing the token in subsequent sessions.
Type: `bool`
Default: `true`
```toml
[app]
use_auth_token_file = true
```
----
#### `auth_token_file`
Paht to the auth token file.
Type: `str`
Default: `"/zabbix-cli/.zabbix-cli_auth_token"`
```toml
[app]
auth_token_file = "/path/to/auth_token_file"
```
----
#### `auth_file`
Paht to a file containing username and password in the format `username:password`. Alternative to specifying `username` and `password` in the configuration file.
Type: `str`
Default: `"/zabbix-cli/.zabbix-cli_auth"`
```toml
[app]
auth_token = "/path/to/auth_file"
```
----
#### `history`
Whether to keep a history of commands.
Type: `bool`
Default: `true`
```toml
[app]
history = true
```
----
#### `history_file`
File for storing the history of commands.
Type: `str`
Default: `"/zabbix-cli/history"`
```toml
[app]
history_file = "/path/to/history_file.history"
```
----
#### `bulk_mode`
Strictness of error handling in bulk operations. If `strict`, the operation will stop at the first error. If `continue`, the operation will continue after errors and report them afterwards. If `skip`, the operation will skip invalid lines in bulk file, as well as ignore all errors when executing the operation.
Type: `str`
Choices: `"strict"`, `"continue"`, `"skip"`
Default: `"strict"`
```toml
[app]
bulk_mode = "strict"
```
----
#### `allow_insecure_auth_file`
Whether to allow insecure auth files.
Type: `bool`
Default: `true`
```toml
[app]
allow_insecure_auth_file = false
```
----
#### `legacy_json_format`
Whether to use the legacy JSON format (pre-Zabbix CLI 3.0), where the output is a JSON mapping with numeric string keys for each result. See the [migration guide](./migration.md) for more information.
Type: `bool`
Default: `false`
```toml
[app]
legacy_json_format = false
```
=== "`app.output`"
The `app.output` section configures the output format of the application.
```toml
[app.output]
format = "table"
color = true
paging = false
theme = "default"
```
----
#### `format`
Format of the application output.
Type: `str`
Default: `"table"`
Choices: `"table"`, `"json"`
```toml
[app.output]
format = "table"
```
----
#### `color`
Whether to use color in the terminal output.
Type: `bool`
Default: `true`
```toml
[app.output]
color = true
```
----
#### `paging`
Whether to use paging in the output.
Type: `bool`
Default: `false`
```toml
[app.output]
paging = false
```
=== "`logging`"
The `logging` section configures logging.
```toml
[logging]
enabled = true
log_level = "INFO"
log_file = "/path/to/zabbix-cli.log"
```
----
#### `enabled`
Whether logging is enabled.
Type: `bool`
Default: `true`
```toml
[logging]
enabled = true
```
----
#### `log_level`
Level for logging.
Type: `str`
Default: `"ERROR"`
Choices: `"DEBUG"`, `"INFO"`, `"WARNING"`, `"ERROR"`, `"CRITICAL"`
```toml
[logging]
log_level = "ERROR"
```
----
#### `log_file`
File for storing logs. Can be omitted to log to stderr (**warning:** NOISY).
Type: `Optional[str]`
Default: `"/zabbix-cli.log"`
```toml
[logging]
log_file = "/path/to/zabbix-cli.log"
```
unioslo-zabbix-cli-09a2fab/docs/guide/index.md 0000664 0000000 0000000 00000000463 14712653334 0021403 0 ustar 00root root 0000000 0000000 # User Guide
Zabbix CLI is an application that provides a command line interface for interacting with the Zabbix API. Once installed, it can be invoked with `zabbix-cli`.
The application is intended to provide a more user-friendly interface to the Zabbix API, and make it easier to automate common tasks.
unioslo-zabbix-cli-09a2fab/docs/guide/installation.md 0000664 0000000 0000000 00000000453 14712653334 0022774 0 ustar 00root root 0000000 0000000 # Installation
The application is primarily distributed with `pip`, but other installation methods are also available.
## Install
{% include-markdown ".includes/quick-install.md" %}
## Upgrade
The upgrade process depends on the chosen installation method.
{% include ".includes/upgrade.md" %}
unioslo-zabbix-cli-09a2fab/docs/guide/logging.md 0000664 0000000 0000000 00000002655 14712653334 0021727 0 ustar 00root root 0000000 0000000 # Logging
The application supports logging to a file or directly to the terminal. By default, file logging is enabled and set to the `ERROR` level.
## Enable/disable logging
Logging is enabled by default. To disable logging, set the `enabled` option to `false` in the configuration file:
```toml
[logging]
enabled = true
```
## Levels
The application only logs messages with a level equal to or higher than the configured level. By default, the level is set to `ERROR`. The available levels are:
- `DEBUG`
- `INFO`
- `WARNING`
- `ERROR`
- `CRITICAL`
The level can be set in the configuration file:
```toml
[logging]
level = "DEBUG"
```
## Log file
The default location of the log file is a file named `zabbix-cli.log` in the application's logs directory.
The log file location can be changed in the configuration file:
```toml
[logging]
log_file = "/path/to/zabbix-cli.log"
```
The default logs directory can be opened with the command:
```bash
zabbix-cli open logs
```
## Log to terminal
!!! warning "Verbose output"
Logging to the terminal can produce a lot of output, especially when the log level is set to `DEBUG`. Furthermore, some of the output messages may be shown twice, as they are printed once by the application and once by the logging library.
If the `log_file` option is set to an empty string or an invalid file path, the application will log to the terminal instead of a file.
```toml
[logging]
log_file = ""
```
unioslo-zabbix-cli-09a2fab/docs/guide/migration.md 0000664 0000000 0000000 00000016727 14712653334 0022277 0 ustar 00root root 0000000 0000000 # Migration Guide
Zabbix CLI 3.0 introduces a whole range of new features and improvements, as well as deprecating some old ones. This guide is intended to help you migrate from Zabbix CLI 2.x to 3.0.
Notable changes include:
**Config**
- [**New configuration file format**](#config-file)
- [**New default configuration file location.**](#new-default-configuration-file-location)
- [**New configuration options**](#new-configuration-options)
- [**Renamed configuration options**](#renamed-configuration-options)
**Exports**
- [**New export formats**](#new-export-formats)
- `yaml`
- `php`
- [**New default export filenames**](#new-default-export-filenames)
- Exported files are no longer prefixed with `zabbix_export_`
- Exported files no longer include a timestamp in the filename by default. Newer exports overwrite older ones automatically.
**Commands**
- [**Command invocation syntax**](#command-invocation-syntax)
- Using `zabbix-cli -C 'command args ...'` is no longer required.
- Commands can be invoked directly with `zabbix-cli command args ...`
- [**Command syntax**](#command-syntax)
- Commands use positional arguments to a lesser degree than in 2.x. Named options are now preferred.
- Legacy positional arguments are deprecated and will generate a warning when used.
- Most prompts have been removed and replaced with named options due to the increase in scope of the commands.
**Output**
- [**JSON output format**](#json-output-format)
- The JSON output format has changed. The old format can be enabled with the `app.legacy_json_format` option in the new TOML configuration file format.
- When using a legacy `.conf` configuration file, the old JSON format is assumed.
## Config file
Multiple changes have been made to the application's configuration file, in terms of format, location and option names.
### New configuration file format
The configuration file is now in [TOML](https://toml.io/en/) format. The old `.conf` format is deprecated but can still be loaded. Old configs generate a warning when used. See [configuration](./configuration.md) for more information on the new format.
An old configuration file can be migrated using the `migrate_config` command:
```bash
zabbix-cli migrate_config
```
The command uses the currently loaded configuration file to generate a new TOML configuration file. The new file is saved in the default TOML configuration file location.
Custom source and destination files can be specified with the `--source` and `--destination` options, respectively:
```bash
zabbix-cli migrate_config --source /path/to/old/config.conf --destination /path/to/new/config.toml
```
### New default configuration file location
The location of the configuration file is now determined by [platformdirs](https://pypi.org/project/platformdirs/). See [Configuration](./configuration.md) for a brief summary of the new default location.
To open the default configuration file directory, use the command:
```bash
zabbix-cli open config
```
### New configuration options
New configuration options have been introduced to the configuration file:
| Option | Description | Default |
| --- | --- | --- |
| `app.default_format` | Default output format in the CLI | `table` |
| `app.legacy_json_format` | Enable [legacy json format](#json-output-format) | `false` |
### Renamed configuration options
Several configuration options have been renamed to better reflect their purpose.
The following table lists the old config section names and their new counterparts:
| Old Config Section | New Config Section |
| --- | --- |
| `zabbix_api` | `api` |
| `zabbix_config` | `app` |
The following table lists the old option names and their new counterparts:
| Old Config Section | Old Option Name | New Config Section | New Option Name |
| --- | --- | --- | --- |
| `zabbix_config` | `zabbix_api_url` | `api` | `url` |
| `zabbix_config` | `cert_verify` | `api` | `verify_ssl` |
| `zabbix_config` | `system_id` | `api` | `username` |
| `zabbix_config` | `default_directory_exports` | `app` | `export_directory` |
| `zabbix_config` | `default_export_format` | `app` | `export_format` |
| `zabbix_config` | `include_timestamp_export_filename` | `app` | `export_timestamps` |
| `logging` | `logging` | `logging` | `enabled` |
For backwards compatibility, all the old option names are still supported, but will be removed in a future version.
See [Sample configuration file](./configuration.md#sample-configuration-file) to see an example of the new configuration file format.
## Exports
### New export formats
Zabbix CLI 3.0 introduces two new export formats: `yaml` and `php`. The availability of these formats depends on the Zabbix version you are using.
Furthermore, the formats are no longer case-sensitive. For example, `YAML` and `yaml` are now equivalent.
### New default export filenames*
Exported files are no longer prefixed with `zabbix_export_`. This behavior can be re-enabled with the `--legacy-filenames` option.
Exported files no longer include a timestamp in the filename by default. Newer exports overwrite older ones automatically. Timestamps can be re-anbled by setting the `app.export_timestamps` option in the configuration file.
## Commands
### Command invocation syntax
In Zabbix CLI 2.x, invoking single commands without entering the REPL required the `-C` option followed by the command and its arguments as a single string:
```bash
zabbix-cli -C 'show_hostgroup "Linux servers"'
```
In Zabbix CLI 3.0, the `-C` option is no longer required. Commands can be invoked directly:
```bash
zabbix-cli show_hostgroup "Linux servers"
```
### Command syntax
In Zabbix CLI 3.0, the majority of positional arguments are replaced with named options. Each command required a specific number of positional arguments that _had_ to be specified. For example, the `export_configuration` command in Zabbix CLI 2.x required the following syntax, even when we wanted to export all hosts:
```bash
zabbix-cli -C 'export_configuration /tmp/zabbix_export.conf hosts #all#'
```
In Zabbix CLI 3.0, the same command would look like this:
```bash
zabbix-cli export_configuration --directory /tmp/exports --type hosts
```
We don't have to pass in a special name argument to indicate that we want to export all hosts. Instead, we can simply omit the `--name` option.
## Output
### JSON output format
In Zabbix CLI 2.x, the output format of commands generally took the form of a JSON mapping with numeric string keys for each result. For example:
```json
{
"0": {
"hostid": "10609",
"host": "foo.example.com",
"groups": [],
// ...
}
}
```
In the new default JSON format introduced in Zabbix CLI 3.0, the output is always a JSON mapping with the keys `message`, `errors`, `return_code` and `result`. For example:
```json
{
"message": "",
"errors": [],
"return_code": "Done",
"result": {
"hostid": "10609",
"host": "foo.example.com",
"groups": [],
// ...
}
}
```
Which means when a command fails to execute or returns an error, the shape of the JSON output will be consistent with the successful output, making it significantly easier to parse:
```json
{
"message": "Host 'foobar.example.com' not found. Check your search pattern and filters.",
"errors": [
"Host 'foobar.example.com' not found. Check your search pattern and filters."
],
"return_code": "Error",
"result": null
}
```
In case of a chain of errors, the application makes an attempt to populate the `errors` array with all the errors encountered during the execution of the command.
unioslo-zabbix-cli-09a2fab/docs/guide/usage.md 0000664 0000000 0000000 00000005535 14712653334 0021405 0 ustar 00root root 0000000 0000000 # Usage
## Interactive mode
Invoking `zabbix-cli` without any arguments will start the application in an interactive shell. This is the default mode of operation, and is the most user-friendly way to use the application.
```bash
zabbix-cli
```
Within the interactive shell, commands can be entered and executed. Command and argument hints, tab autocompletion and history are supported out of the box.
```
% zabbix-cli
╭────────────────────────────────────────────────────────────╮
│ Welcome to the Zabbix command-line interface (v3.0.0) │
│ Connected to server http://localhost:8082 (v7.0.0) │
╰────────────────────────────────────────────────────────────╯
Type --help to list commands, :h for REPL help, :q to exit.
>
```
## Single command mode
Commands can also be invoked directly from the command line. This is useful for scripting and automation, as well for just running one-off commands.
```bash
zabbix-cli show_hostgroup "Linux servers"
```
## Bulk mode
Zabbix CLI also supports running commands sourced from a file with the `--file` option. This is useful for running a series of commands in bulk.
The file should contain one command per line, with arguments separated by spaces. Comments can be added with `#`.
```
$ cat /path/to/commands.txt
# This is a comment
show_hostgroup "Linux servers"
create_host --host "foo.example.com" --hostgroup "Linux servers,Applications" --proxy .+ --status on --no-default-hostgroup --description "Added in bulk mode"
create_hostgroup "My new group"
add_host_to_hostgroup foo.example.com "My new group"
```
```
$ zabbix-cli --file /path/to/commands.txt
╭────┬───────────────┬───────┬───────╮
│ ID │ Name │ Flag │ Hosts │
├────┼───────────────┼───────┼───────┤
│ 2 │ Linux servers │ Plain │ │
╰────┴───────────────┴───────┴───────╯
✓ Created host 'foobarbaz.example.com' (10634)
✓ Created host group My new group (31).
╭──────────────┬───────────────────────╮
│ Hostgroup │ Hosts │
├──────────────┼───────────────────────┤
│ My new group │ foobarbaz.example.com │
╰──────────────┴───────────────────────╯
✓ Added 1 host to 1 host group.
```
unioslo-zabbix-cli-09a2fab/docs/index.md 0000664 0000000 0000000 00000000603 14712653334 0020302 0 ustar 00root root 0000000 0000000 # Zabbix CLI
Zabbix CLI is a command line application for interacting with Zabbix version 6 or later. It is written in Python and uses the Zabbix API to interact with a Zabbix server.
## Installation
{% include-markdown ".includes/quick-install.md" %}
For the next steps or ways to customize the installation, head over to the detailed [installation](./guide/installation.md) guide.
unioslo-zabbix-cli-09a2fab/docs/plugins/ 0000775 0000000 0000000 00000000000 14712653334 0020333 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/plugins/external-plugins.md 0000664 0000000 0000000 00000011432 14712653334 0024157 0 ustar 00root root 0000000 0000000 # External plugins
!!! important
This page assumes you have read the [Writing plugins](./guide.md) page to understand the basics of writing plugins.
External plugins are plugins that are packaged as Python packages and can be installed with Pip. Using [`pyproject.toml` entry points](https://packaging.python.org/en/latest/specifications/entry-points/), the application can automatically discover and load these plugins.
A complete example of an external plugin can be found here:
## Packaging
Assuming you have written a plugin module as outlined in[Writing plugins](./guide.md), you can package it as a Python package that defines an entry point for Zabbix-CLI to discover. Similar to local plugins, the entry point is a Python file or module that contains the plugin's functionality, except for external plugins, the entry point is defined in the `pyproject.toml` file - _not_ the configuration file.
### Directory structure
The plugin package should have the following directory structure:
```plaintext
.
├── my_plugin/
│ ├── __init__.py
│ └── plugin.py
└── pyproject.toml
```
Alternatively, if using the src layout:
```plaintext
.
├── src/
│ └── my_plugin/
│ ├── __init__.py
│ └── plugin.py
└── pyproject.toml
```
### pyproject.toml
The package must contain a `pyproject.toml` file that instructs your package manager how to build and install the package. The following is a good starting point for a project using `hatchling` as the build backend:
```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my_plugin"
authors = [
{name = "Firstname Lastname", email = "mail@example.com"},
]
version = "0.1.0"
description = "My first Zabbix CLI plugin"
readme = "README.md"
requires-python = ">=3.8"
license = "MIT"
dependencies = [
"zabbix-cli@git+https://github.com/unioslo/zabbix-cli.git",
]
[tool.hatch.metadata]
allow-direct-references = true
[project.entry-points.'zabbix-cli.plugins']
my_plugin = "my_plugin.plugin"
```
!!! info "Build backend"
If you prefer setuptools, you can omit the `[tool.hatch.metadata]` section and replace the `[build-system]` section with the following:
```toml
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
```
#### Declaring the entry point
In your plugin's `pyproject.toml` file, you _must_ declare an entry point that Zabbix-CLI can find and load. The entry point is defined in the `[project.entry-points.'zabbix-cli.plugins']` section, where the key is the name of the plugin and the value is the import path to your plugin module. Recall that we defined a directory structure like this:
```plaintext
.
├── my_plugin/
│ ├── __init__.py
│ └── plugin.py
└── pyproject.toml
```
In which case, the entry point should be defined as follows:
```toml
[project.entry-points.'zabbix-cli.plugins']
my_plugin = "my_plugin.plugin"
```
## Configuration
!!! info "Loading external plugins"
External plugins are automatically discovered by the application and do not require manual configuration to be loaded.
Much like local plugins, external plugins define their configuration in the application's configuration file. However, the configuration is not used to _load_ the plugin, and is only used to provide additional configuration options or customization.
The name of the plugin in the configuration file must match the name used in the entry point section in the `pyproject.toml` file. Given that we used the name `my_plugin` in the entrypoint section, its configuration should look like this in the Zabbix-CLI configuration file:
```toml
[plugins.my_plugin]
# module must be omitted for external plugins
enabled = true
extra_option_1 = "Some value"
extra_option_2 = 42
```
!!! warning "Local plugin migration"
If rewriting a local plugin as an external one, remember to remove the `module` key from the plugin's configuration. If a `module` key is present, the application will attempt to load the plugin as a local plugin.
## Installation
How to install the plugins depends on how Zabbix-CLI is installed. The plugin must be installed in the same Python environment as Zabbix-CLI, which is different for each installation method.
### uv
`uv` can install plugins using the same `uv tool install` command, but with the `--with` flag:
```bash
uv tool install zabbix-cli-uio --with my_plugin
```
### pipx
`pipx` Zabbix-CLI installations require the plugin to be injected into the environment:
```bash
pipx install zabbix-cli-uio
pipx inject zabbix-cli-uio my_plugin
```
### pip
If Zabbix-CLI is installed with `pip`, the plugin can be installed as a regular Python package:
```bash
pip install my_plugin
```
unioslo-zabbix-cli-09a2fab/docs/plugins/guide.md 0000664 0000000 0000000 00000043353 14712653334 0021762 0 ustar 00root root 0000000 0000000 # Writing plugins
This guide goes over everything required to write a local plugin loaded by the application on startup.
## Basics
### Directory structure
Start off by creating a new directory containing a Python module that defines the plugin, as well as an `__init__.py` file:
```plaintext
.
└── my_plugin/
├── __init__.py
└── plugin.py
```
The `__init__.py` file can be empty, but it's a good practice to define one for Python to treat the directory as a package. The `plugin.py` file is where the plugin's functionality is defined. Defining your plugin as a package lets you split your plugin into multiple files, making it easier to manage as it grows in complexity. It also allows you to more easily publish the plugin as an external package later on should you choose to do so.
### Module
In order to define new commands in our plugin module, we need to import `zabbix_cli.app.app`. This is the main Typer application object that we will use to access the application state and define new commands with. A simple command that prints a message to the console can be defined like this:
```python
# /path/to/my_plugin/plugin.py
from zabbix_cli.app import app
@app.command(name="my_command")
def my_command() -> None:
print("Hello, world!")
```
### Activating the plugin
We will look at plugin configuration more in-depth in the [Configuration](#configuration) section, but for now, we can add the plugin to the configuration file like this:
```toml
[plugins.my_plugin]
module = "/path/to/my_plugin/my_plugin.py"
```
This tells the application to load the plugin module when it starts up.
Running `zabbix-cli --help` should now show the new command in the list of available commands.
{ width="100%" }
The command from the plugin is loaded
However, we can see that the command does not have a description or belong to any particular category. In the next section we will look at adding help text to commands and defining a category.
### Help text and categories
Commands can have a long and short description and belong to a category. The category is used to group commands in the help output. We can define a category for our plugin commands by providing an argument to the `rich_help_panel` parameter when defining the command. This will add a new section to the help output with the given name we chose.
```python
RICH_HELP_PANEL = "My custom commands"
@app.command(name="my_command", rich_help_panel=RICH_HELP_PANEL)
def my_command() -> None:
"""Short description of the command.
Longer description going over the command in more detail.
"""
print("Hello, world!")
```
The command will now be added to its own category in the help output:
{ width="100%" }
Invoking `zabbix-cli my_command --help` shows the long description we provided:
{ width="100%" }
### Printing messages
One of the most common operations in a CLI is printing messages to the console. The application provides several convience methods for printing messages to the console with specific formatting, which we can use in our commands to print messages consistent with the rest of the application. These are:
- `zabbix_cli.output.console.success`
- `zabbix_cli.output.console.info`
- `zabbix_cli.output.console.warning`
- `zabbix_cli.output.console.error`
As well as [Rich](https://rich.readthedocs.io/en/latest/introduction.html) Console objects for stdout and stderr for more advanced formatting:
- `zabbix_cli.output.console.console`
- `zabbix_cli.output.console.err_console`
We can use these to print to the console like this:
```python
from zabbix_cli.app import app
from zabbix_cli.output.console import success
from zabbix_cli.output.console import info
from zabbix_cli.output.console import warning
from zabbix_cli.output.console import error
from zabbix_cli.output.console import err_console
from zabbix_cli.output.console import console
@app.command(name="my_command")
def my_command() -> None:
success("Success message")
info("Info message")
warning("Warning message")
error("Error message")
err_console.print("Error message")
console.print("Output of some sort")
```
This will print messages to the console using the same formatting as the built-in commands:
{ width="100%" }
### Command arguments
In general, it is best to refer to the [Typer](https://typer.tiangolo.com/tutorial/) documentation when it comes to defining command arguments. However, a minimal example is provided here, showing how to define a command with a positional argument and a named option and use them to interact with the Zabbix API client:
```python
from typing import Optional
import typer
from zabbix_cli.app import app
from zabbix_cli.render import render_result
# Define a new command
@app.command(name="my_command")
def my_command(
arg1: str = typer.Argument(help="Some positional argument"),
opt1: Optional[str] = typer.Option(None, "--opt1", "-O", help="Some named option"),
) -> None:
"""Short description of the command."""
# We can use the Zabbix API client
host = app.state.client.get_host(arg1)
# We can use the same rendering machinery as the built-in commands
render_result(host)
```
### Post-import configuration
The module can define a function called `__configure__` that will be called after the application has finished its own configuration. This function can be used to perform any necessary setup or configuration that the plugin requires. The function takes a single `PluginConfig` argument.
```python
from zabbix_cli.app import app
import logging
logger = logging.getLogger(__name__)
def __configure__(config: PluginConfig) -> None:
logger.info(f"Running post-import configuration for {config.module}")
# We can access anything we need from the application state as long as the plugin module imports `zabbix_cli.app.app`
# Set custom HTTP headers
app.state.client.session.headers["X-Plugin-Header"] = "Some value"
# Ensure that a certain configuration key is set
app.state.config.api.legacy_json_format = False
```
You are free to perform any configuration you want in the `__configure__` function. However, be aware that modifying certain config options, especially those found in `app.state.config.api`, will not have any effect on the rest of the application. By the time `__configure__` is called, the application has already configured the API client.
### Configuration file
Plugins are configured in the application's configuration file. Given that we have a plugin named `my_plugin`, its configuration file entry should look like this:
```toml
[plugins.my_plugin]
```
Depending on whether you are writing a local or external plugin, the configuration requires different options. External plugins do not require _any_ configuration by default, while local plugins _must_ have a `module` key defined.
#### `module`
Local plugins must define this key. Its value can be a module path or a a file path. If using a file path, it is highly recommended to use an absolute path.
```toml
[plugins.my_plugin]
module = "/path/to/my_plugin/my_plugin.py"
# OR
# module = "path.to.my_plugin"
```
#### `enabled`
Enable or disable plugin. Plugins are enabled by default unless otherwise specified.
```toml
[plugins.my_plugin]
enabled = false
```
#### `optional`
Mark a plugin as optional, meaning the application will not exit if the plugin module cannot be imported. This is useful for plugins that are not required for the application to function. Plugins are not optional by default.
```toml
[plugins.my_plugin]
optional = true
```
#### Extra options
The plugin configuration can contain any number of extra options that the plugin module can access. These options can be accessed through the `PluginConfig` object that is passed to the `__configure__` function.
```toml
[plugins.my_plugin]
module = "path.to.my_plugin"
extra_option_str = "foo"
extra_option_int = 42
extra_option_list = ["a", "b", "c"]
```
The `PluginConfig.get()` method can be used to retrieve the value of these extra options. The method takes the key of the option as the first argument, and an optional default value as the second argument. The method also takes an optional type hint as the third argument `type`.
```python
from zabbix_cli.app import app
from zabbix_cli.config.model import PluginConfig
def __configure__(config: PluginConfig) -> None:
# Access extra options
opt1 = config.get("extra_option_str")
# Validate the type of the option
# Also lets type checkers know the type of the variable
opt1 = config.get("extra_option_str", type=str)
# Types are optional
opt2 = config.get("extra_option_int")
# reveal_type(opt2) # reveals Any because no type hint
# Types from the TOML file are preserved
assert isinstance(opt2, int)
# We can validate more complex types too
opt4 = config.get("extra_option_list", type=list[str])
# reveal_type(opt4) # reveals list[str]
# We can also provide a default value
opt4 = config.get("non_existent_option", "default")
assert opt4 == "default"
# Type hints are supported here too
opt5 = config.get("non_existent_option", "default", type=str)
# reveal_type(opt5) # reveals str
assert opt5 == "default"
# Use our config options:
app.state.client.session.headers["X-Plugin-Header"] = config.get(
"extra_option_str", type=str
)
```
!!! tip
Providing a type for the `get()` method will also give you better auto completion and type checking in your editor.
### Accessing plugin configuration from commands
Inside commands, the plugin's configuration can be accessed through the `app.get_plugin_config()` method.
The name of the plugin, as denoted by its `[plugins.]` key, is passed as the argument to the method. If no configuration can be found, an empty `PluginConfig` object is returned.
Given the following configuration:
```toml
[plugins.my_plugin]
```
We can access its configuration like this:
```python
from zabbix_cli.app import app
@app.command()
def my_command() -> None:
config = app.get_plugin_config("my_plugin")
```
!!! note
Should no config be available, an empty `PluginConfig` is returned. This is to facilitate external plugins that do not _require_ a configuration to be defined.
## Advanced
### Rendering
Most zabbix-cli commands render a table or JSON depdening on the active output format. The functionality that powers this is the `zabbix_cli.models.TableRenderable` class. This class is a Pydantic model that can be subclassed and used to define data models that the application can render.
```python
from typing import List
from zabbix_cli.models import TableRenderable
from zabbix_cli.output.render import render_result
class MyModel(TableRenderable):
host: str
status: str
ip: str
templates: List[str]
@app.command(name="my_command")
def my_command() -> None:
m = MyModel(
host="foo.example.com",
status="Good",
ip="192.168.0.2",
templates=["Template OS Linux", "Template App MySQL"],
)
render_result(m)
```
Invoking the command will render the model as a table:
```bash
zabbix-cli my_command
```
{ width="75%" }
Adding `-o json` will render the model as JSON:
```bash
zabbix-cli -o json my_command
```
```json
{
"message": "",
"errors": [],
"return_code": "Done",
"result": {
"host": "foo.example.com",
"status": "Good",
"ip": "192.168.0.2",
"templates": [
"Template OS Linux",
"Template App MySQL"
]
}
}
```
### Field-level customization
By default, column headers and cells are determined by the field names and values of the model. We can customize this behavior by adding metadata to the model fields using something called "Meta Keys". These are special keys that can be added to the `json_schema_extra` dict of a field to change how it is rendered.
#### Column headers
If we just want to change the column header for a single field, we can pass a `zabbix_cli.models.MetaKey` object to the field's `json_schema_extra` dict when defining it:
```python
from pydantic import Field
from zabbix_cli.models import MetaKey
from zabbix_cli.models import TableRenderable
class MyModel(TableRenderable):
host: str
status: str
ip: str = Field(..., json_schema_extra={MetaKey.HEADER: "IP Address"})
templates: List[str]
```
This will change the column header for the `ip` field to "IP Address":
{ width="75%" }
#### Lists
Lists are rendered as newline-separated strings by default. We can change this by passing a `zabbix_cli.models.MetaKey` object to the field's `json_schema_extra` dict with the `MetaKey.JOIN_CHAR` key set to the desired separator:
```python
from pydantic import Field
from zabbix_cli.models import MetaKey
from zabbix_cli.models import TableRenderable
class MyModel(TableRenderable):
host: str
status: str
ip: str
templates: List[str] = Field(..., json_schema_extra={MetaKey.JOIN_CHAR: ", "})
```
This will render the `templates` field as a comma-separated string:
{ width="75%" }
## Example
A complete example of a plugin that defines a new command and uses the plugin configuration to set a custom HTTP header on the Zabbix API client:
```python
# /path/to/my_plugin.py
from __future__ import annotations
from typing import Optional
import typer
from zabbix_cli.app import app
from zabbix_cli.render import render_result
# Header for the rich help panel shown in the --help output
CATEGORY = "My custom commands"
def __configure__(config: PluginConfig) -> None:
app.state.client.session.headers["X-Plugin-Header"] = config.get("extra_option_str", type=str)
@app.command(name="my_command", rich_help_panel=CATEGORY)
def my_command(
arg1: str = typer.Argument(help="Some positional argument"),
opt1: Optional[str] = typer.Option(None, "--opt1", "-O", help="Some named option"),
) -> None:
"""Short description of the command."""
host = app.state.client.get_host(arg1)
render_result(host)
```
And the corresponding configuration:
```toml
[plugins.my_plugin]
module = "path.to.my_plugin"
enabled = true
optional = false
extra_option_str = "foo"
```
An example of an external plugin with several commands, tests, and a `pyproject.toml` file can be found here:
## Performance
The application at large makes use of inline imports inside functions to improve the startup time of the application. For the most part, the modules that are most the important to lazily import are ones that define Pydantic models. In order of performance impact, they are:
1. `zabbix_cli.pyzabbix.types`
2. `zabbix_cli.pyzabbix.models`
3. `zabbix_cli.commands.results`
### Inline imports
Consider creating a separate module for your own models that you can import inside your commands that need them. This will prevent a cascade of imports that can add several hundred milliseconds of startup time to the application. Pydantic is notoriously slow at defining models, so avoiding importing these modules until they are needed is crucial.
**BEFORE**
```python
# /path/to/my_plugin.py
import typer
from zabbix_cli.app import app
from zabbix_cli.models import TableRenderable
from zabbix_cli.output.render import render_result
from zabbix_cli.pyzabbix.types import Host
class MyModel(TableRenderable):
host: Host
@app.command(name="my_command")
def my_command(ctx: typer.Context, name: str = typer.Argument()) -> None:
host = app.state.client.get_host(name)
model = MyModel(host=host, foo="foo", bar=42)
render_result(model)
```
**AFTER**
```python
# /path/to/models.py
from zabbix_cli.models import TableRenderable
from zabbix_cli.pyzabbix.types import Host
class MyModel(TableRenderable):
host: Host
foo: str
bar: int
# /path/to/plugin.py
import typer
from zabbix_cli.app import app
from zabbix_cli.output.render import render_result
@app.command(name="my_command")
def my_command(ctx: typer.Context, name: str = typer.Argument()) -> None:
from .models import MyModel # or use absolute import
host = app.state.client.get_host(name)
model = MyModel(host=host, foo="foo", bar=42)
render_result(model)
```
### Profiling
!!! warning
`py-spy` does not support Python 3.12 at the time of writing.
Consider using [`py-spy`](https://github.com/benfred/py-spy) to profile the application before you package and distribute your plugin to ensure that it does not have a significant impact on the application's startup time. Profiling `--help` lets us profile the application startup time before any network I/O can occur.
Install `py-spy` with:
```bash
pip install py-spy
```
We can then use `py-spy` to profile the different imports the application performs and generate an SVG file:
```bash
sudo py-spy record --subprocesses -o profile.svg --format speedscope -- zabbix-cli --help
```
The generated SVG file can be viewed on its own, or uploaded to the [speedscope](https://www.speedscope.app/) web application for a more interactive experience.
{ width="100%" }
Visual profiling with speedscope
The width of a bar indicates the time in milliseconds the given import adds, while its height indicates the number of stack frames generated as a result. A wide bar indicates a slow import, and can often be traced back to a cascade of numerous dependent imports and/or specific time-consuming imports.
unioslo-zabbix-cli-09a2fab/docs/plugins/index.md 0000664 0000000 0000000 00000004053 14712653334 0021766 0 ustar 00root root 0000000 0000000 # Plugins
!!! warning "Work in progress"
The plugin system is still under development and may change in future releases.
The functionality of the application can be extended with user-defined plugins. Plugins can be used to add new commands, modify existing commands, or add new functionality to the application. Plugins can installed as local Python modules or external Python packages.
## Local plugins
Local plugins are local Python modules (files) that are loaded by the application. They are the easiest way to add new functionality to the application, but are harder to distribute and share in a consistent manner. They are not automatically discovered by the application, and must be manually configured in the configuration file.
See the [local plugins](./local-plugins.md) page for more information.
## External plugins
External plugins are Python packages that can be installed with Pip and are automatically discovered by the application. They are easier to distribute and share, but require more effort on the part of the plugin author to create and maintain.
See the [external plugins](./external-plugins.md) page for more information.
## Choosing the correct plugin type
Both local and external plugins are essentially written in the same manner, following the application’s guidelines for plugin development outlined in [Writing plugins](./guide.md). This common foundation ensures that the core functionality is consistent whether the plugin is distributed as a local module or an external package.
The difference lies primarily in how they are packaged for distribution and how the application loads them. While local plugins require manual configuration to be recognized by the application, external plugins are designed to be discovered automatically once installed.
An easy way to decide which type of plugin to use is to consider whether you intend to share your plugin or not. If you do, an external plugin is likely the way to go. If you are developing a plugin for personal use or for a specific environment, a local plugin may be more appropriate.
unioslo-zabbix-cli-09a2fab/docs/plugins/local-plugins.md 0000664 0000000 0000000 00000002621 14712653334 0023427 0 ustar 00root root 0000000 0000000 # Local plugins
!!! important
This page assumes you have read the [Writing plugins](./guide.md) page to understand the basics of writing plugins.
A local plugin is a Python module that is loaded by the application on startup. It _must_ be manually configured in the configuration file for the application to find it.
## Directory structure
Given your plugin is structured like this:
```plaintext
/path/to/
└── my_plugin/
├── __init__.py
└── plugin.py
```
You can add the following to your configuration file:
```toml
[plugins.my_plugin]
module = "/path/to/my_plugin/plugin.py"
# or
# module = "my_plugin.plugin"
```
An absolute path to the plugin file is preferred, but a Python module path can also be used. The differences are outlined below.
### File path
It is recommended to use an absolute path to the plugin file. This ensures that the application can find the plugin regardless of the current working directory. The path should point to the plugin file itself, not the directory containing it.
### Module path
One can also use a Python module path to the plugin file. This is useful if the plugin is part of a larger Python package. The path must be available in the Python path (`$PYTHONPATH`) for the application to find it. The import path can point to the plugin file itself or the directory containing it as long as `__init__.py` is present and imports the plugin file.
unioslo-zabbix-cli-09a2fab/docs/scripts/ 0000775 0000000 0000000 00000000000 14712653334 0020341 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/scripts/__init__.py 0000664 0000000 0000000 00000000000 14712653334 0022440 0 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/scripts/common.py 0000664 0000000 0000000 00000000756 14712653334 0022213 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from pathlib import Path
# Directory of all docs files
DOC_DIR = Path(__file__).parent.parent
# Directory of data files for Jinja2 templates
DATA_DIR = DOC_DIR / "data"
if not DATA_DIR.exists():
DATA_DIR.mkdir(parents=True)
# Directory of Jinja2 templates
TEMPLATES_DIR = DOC_DIR / "templates"
# Directory of generated command doc pages
COMMANDS_DIR = DOC_DIR / "guide" / "commands"
if not COMMANDS_DIR.exists():
COMMANDS_DIR.mkdir(parents=True)
unioslo-zabbix-cli-09a2fab/docs/scripts/gen_cli_data.py 0000664 0000000 0000000 00000003476 14712653334 0023316 0 ustar 00root root 0000000 0000000 """Script that runs various CLI commands and collects the result for use
in the documentation.
The commands are run in a limited environment (no color, limited width) to
make the output more readable in the documentation.
"""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
from typing import NamedTuple
from typing import Optional
from typing import Protocol
import tomli
import tomli_w
sys.path.append(Path(__file__).parent.as_posix())
from common import DATA_DIR # noqa
# Set up environment variables for the CLI
env = os.environ.copy()
env["LINES"] = "40"
env["COLUMNS"] = "90" # limit width so it looks nicer in MD code blocks
env["TERM"] = "dumb" # disable color output (color codes mangle it)
class CommandCallback(Protocol):
def __call__(self, output: str) -> str: ...
class Command(NamedTuple):
command: list[str]
filename: str
callback: Optional[CommandCallback] = None
def add_config_bogus_defaults(output: str) -> str:
"""Give bogus defaults to certain config values."""
config = tomli.loads(output)
# TODO: replace local username with a default value
return tomli_w.dumps(config)
COMMAND_HELP = Command(["zabbix-cli", "--help"], "help.txt")
COMMAND_SAMPLE_CONFIG = Command(
["zabbix-cli", "sample_config"],
"sample_config.toml",
callback=add_config_bogus_defaults,
)
# List of commands to run
COMMANDS = [
COMMAND_HELP,
COMMAND_SAMPLE_CONFIG,
]
def main() -> None:
"""Run the commands and save the output to files."""
for cmd in COMMANDS:
output = subprocess.check_output(cmd.command, env=env).decode("utf-8")
if cmd.callback:
output = cmd.callback(output)
with open(DATA_DIR / cmd.filename, "w") as f:
f.write(output)
if __name__ == "__main__":
main()
unioslo-zabbix-cli-09a2fab/docs/scripts/gen_cli_options.py 0000664 0000000 0000000 00000004501 14712653334 0024066 0 ustar 00root root 0000000 0000000 """Generates a YAML file containing all the global options for the CLI."""
from __future__ import annotations
import sys
from pathlib import Path
from typing import NamedTuple
import yaml # type: ignore
from zabbix_cli.main import app
sys.path.append(Path(__file__).parent.as_posix())
from common import DATA_DIR # noqa
from utils.commands import get_app_callback_options # noqa
def convert_envvar_value(text: str | list[str] | None) -> list[str] | None:
# The envvars might actually be instances of `harbor_cli.config.EnvVar`,
# which the YAML writer does not convert to strings. Hence `str(...)`
if isinstance(text, list):
return [str(t) for t in text]
elif isinstance(text, str):
# convert to str (might be enum) and wrap in list
return [str(text)]
elif text is None:
return []
else:
raise ValueError(f"Unexpected option env var type {type(text)} ({text})")
# name it OptInfo to avoid confusion with typer.models.OptionInfo
class OptInfo(NamedTuple):
params: list[str]
help: str | None
envvar: list[str]
config_value: str | None
@property
def fragment(self) -> str | None:
if self.config_value is None:
return None
return self.config_value.replace(".", "")
def to_dict(self) -> dict[str, str | list[str] | None]:
return {
"params": ", ".join(f"`{p}`" for p in self.params),
"help": self.help or "",
"envvar": convert_envvar_value(self.envvar),
"config_value": self.config_value,
"fragment": self.fragment,
}
def main() -> None:
options = [] # type: list[OptInfo]
for option in get_app_callback_options(app):
if not option.param_decls:
continue
conf_value = None
if hasattr(option, "config_override"):
conf_value = option.config_override
h = option._help_original if hasattr(option, "_help_original") else option.help
o = OptInfo(
params=option.param_decls,
help=h,
envvar=option.envvar,
config_value=conf_value,
)
options.append(o)
to_dump = [o.to_dict() for o in options]
with open(DATA_DIR / "options.yaml", "w") as f:
yaml.dump(to_dump, f, sort_keys=False)
if __name__ == "__main__":
main()
unioslo-zabbix-cli-09a2fab/docs/scripts/gen_command_list.py 0000664 0000000 0000000 00000002430 14712653334 0024214 0 ustar 00root root 0000000 0000000 """Generate documentation of commands and categories.
Generates the following files:
- `commandlist.yaml`: List with names of all commands.
- `commands.yaml`: Mapping of all categories to detailed information
about each command.
"""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
import yaml # type: ignore
from zabbix_cli.app import app
sys.path.append(Path(__file__).parent.as_posix())
from common import DATA_DIR # noqa
from utils.commands import get_app_commands # noqa: E402
def main() -> None:
commands = get_app_commands(app)
command_names = [c.name for c in commands]
categories: Dict[str, List[Dict[str, Any]]] = {}
for command in commands:
category = command.category or ""
if category not in categories:
categories[category] = []
cmd_dict = command.model_dump(mode="json")
# cmd_dict["usage"] = command.usage
categories[category].append(cmd_dict)
with open(DATA_DIR / "commands.yaml", "w") as f:
yaml.dump(categories, f, sort_keys=True)
with open(DATA_DIR / "commandlist.yaml", "w") as f:
yaml.dump(command_names, f, sort_keys=True)
if __name__ == "__main__":
main()
unioslo-zabbix-cli-09a2fab/docs/scripts/gen_commands.py 0000664 0000000 0000000 00000005445 14712653334 0023355 0 ustar 00root root 0000000 0000000 """Generate the code reference pages and navigation."""
from __future__ import annotations
import sys
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
import jinja2
import yaml # type: ignore
from sanitize_filename import sanitize
from zabbix_cli.app import app
sys.path.append(Path(__file__).parent.as_posix())
sys.path.append(Path(__file__).parent.parent.parent.as_posix())
from common import COMMANDS_DIR # noqa
from common import DATA_DIR # noqa
from common import TEMPLATES_DIR # noqa
from utils.commands import CommandSummary # noqa: E402
from utils.commands import get_app_commands # noqa: E402
def gen_command_list(commands: list[CommandSummary]) -> None:
"""Generates a YAML file with a list of the names of all commands."""
command_names = [c.name for c in commands]
with open(DATA_DIR / "commandlist.yaml", "w") as f:
yaml.dump(command_names, f, sort_keys=False)
def gen_category_command_map(commands: list[CommandSummary]) -> None:
"""Generates a YAML file with all categories and detailed information
about their respective commands.
"""
categories: Dict[str, List[Dict[str, Any]]] = {}
for command in commands:
category = command.category or ""
if category not in categories:
categories[category] = []
cmd_dict = command.model_dump(mode="json")
# cmd_dict["usage"] = command.usage
categories[category].append(cmd_dict)
with open(DATA_DIR / "commands.yaml", "w") as f:
yaml.dump(categories, f, sort_keys=True)
def gen_category_pages(commands: list[CommandSummary]) -> None:
"""Renders markdown pages for each category with detailed information
about each command.
"""
categories: Dict[str, List[CommandSummary]] = {}
for command in commands:
if command.hidden:
continue
category = command.category or command.name
if category not in categories:
categories[category] = []
categories[category].append(command)
loader = jinja2.FileSystemLoader(searchpath=TEMPLATES_DIR)
env = jinja2.Environment(loader=loader)
# Render each individual command page
pages = {} # type: dict[str, str] # {category: filename}
for category_name, cmds in categories.items():
template = env.get_template("category.md.j2")
filename = sanitize(category_name.replace(" ", "_"))
filepath = COMMANDS_DIR / f"{filename}.md"
with open(filepath, "w") as f:
f.write(template.render(category=category_name, commands=cmds))
pages[category_name] = filename
def main() -> None:
commands = get_app_commands(app)
gen_category_command_map(commands)
gen_command_list(commands)
gen_category_pages(commands)
if __name__ == "__main__":
main()
unioslo-zabbix-cli-09a2fab/docs/scripts/gen_formats.py 0000664 0000000 0000000 00000000711 14712653334 0023216 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from pathlib import Path
import yaml # type: ignore
from zabbix_cli.config.constants import OutputFormat
sys.path.append(Path(__file__).parent.as_posix())
from common import DATA_DIR # noqa
def main() -> None:
fmts = [fmt.value for fmt in OutputFormat]
with open(DATA_DIR / "formats.yaml", "w") as f:
yaml.dump(fmts, f, default_flow_style=False)
if __name__ == "__main__":
main()
unioslo-zabbix-cli-09a2fab/docs/scripts/gen_ref_pages.py 0000664 0000000 0000000 00000001316 14712653334 0023500 0 ustar 00root root 0000000 0000000 """Generate the code reference pages."""
from __future__ import annotations
from pathlib import Path
import mkdocs_gen_files
src = Path(__file__).parent.parent / "src"
for path in sorted(src.rglob("*.py")):
module_path = path.relative_to(src).with_suffix("")
doc_path = path.relative_to(src).with_suffix(".md")
full_doc_path = Path("reference", doc_path)
parts = tuple(module_path.parts)
if parts[-1] == "__init__":
parts = parts[:-1]
elif parts[-1] == "__main__":
continue
with mkdocs_gen_files.open(full_doc_path, "w") as fd:
identifier = ".".join(parts)
print("::: " + identifier, file=fd)
mkdocs_gen_files.set_edit_path(full_doc_path, path)
unioslo-zabbix-cli-09a2fab/docs/scripts/run.py 0000664 0000000 0000000 00000001020 14712653334 0021510 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import sys
from pathlib import Path
from typing import Any
sys.path.append(Path(__file__).parent.as_posix())
import docs.scripts.gen_commands as gen_commands # noqa
import gen_cli_data # noqa
import gen_cli_options # noqa
import gen_command_list # noqa
import gen_formats # noqa
def main(*args: Any, **kwargs: Any) -> None:
for mod in [
gen_cli_data,
gen_cli_options,
gen_command_list,
gen_commands,
gen_formats,
]:
mod.main()
unioslo-zabbix-cli-09a2fab/docs/scripts/utils/ 0000775 0000000 0000000 00000000000 14712653334 0021501 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/scripts/utils/__init__.py 0000664 0000000 0000000 00000000000 14712653334 0023600 0 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/scripts/utils/commands.py 0000664 0000000 0000000 00000026030 14712653334 0023655 0 ustar 00root root 0000000 0000000 from __future__ import annotations
from functools import lru_cache
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Union
from typing import cast
import click
import typer
from pydantic import BaseModel
from pydantic import Field
from pydantic import ValidationError
from pydantic import computed_field
from pydantic import model_validator
from typer.core import TyperArgument
from typer.core import TyperCommand
from typer.core import TyperGroup
from typer.models import DefaultPlaceholder
from zabbix_cli.exceptions import ZabbixCLIError
from .markup import markup_as_plain_text
from .markup import markup_to_markdown
def get(param: Any, attr: str) -> Any:
"""Getattr that defaults to None"""
return getattr(param, attr, None)
class ParamSummary(BaseModel):
"""Serializable representation of a click.Parameter."""
allow_from_autoenv: Optional[bool] = None
confirmation_prompt: Optional[bool] = None
choices: Optional[List[str]] = None
count: Optional[bool] = None
default: Optional[Any] = None
envvar: Optional[str]
expose_value: bool
flag_value: Optional[Any] = None
help: str
hidden: Optional[bool] = None
human_readable_name: str
is_argument: bool
is_eager: bool = False
is_bool_flag: Optional[bool] = None
is_flag: Optional[bool] = None
is_option: Optional[bool]
max: Optional[int] = None
min: Optional[int] = None
metavar: Optional[str]
multiple: bool
name: Optional[str]
nargs: int
opts: List[str]
prompt: Optional[str] = None
prompt_required: Optional[bool] = None
required: bool
secondary_opts: List[str] = []
show_choices: Optional[bool] = None
show_default: Optional[bool] = None
show_envvar: Optional[bool] = None
type: str
@classmethod
def from_param(cls, param: click.Parameter) -> ParamSummary:
"""Construct a new ParamSummary from a click.Parameter."""
try:
help_ = param.help or "" # type: ignore
except AttributeError:
help_ = ""
is_argument = isinstance(param, (click.Argument, TyperArgument))
return cls(
allow_from_autoenv=get(param, "allow_from_autoenv"),
confirmation_prompt=get(param, "confirmation_prompt"),
count=get(param, "count"),
choices=get(param.type, "choices"),
default=param.default,
envvar=param.envvar, # TODO: support list of envvars
expose_value=param.expose_value,
flag_value=get(param, "flag_value"),
help=help_,
hidden=get(param, "hidden"),
human_readable_name=param.human_readable_name,
is_argument=is_argument,
is_bool_flag=get(param, "is_bool_flag"),
is_eager=param.is_eager,
is_flag=get(param, "is_flag"),
is_option=get(param, "is_option"),
max=get(param.type, "max"),
min=get(param.type, "min"),
metavar=param.metavar,
multiple=param.multiple,
name=param.name,
nargs=param.nargs,
opts=param.opts,
prompt=get(param, "prompt"),
prompt_required=get(param, "prompt_required"),
required=param.required,
secondary_opts=param.secondary_opts,
show_choices=get(param, "show_choices"),
show_default=get(param, "show_default"),
show_envvar=get(param, "show_envvar"),
type=param.type.name,
)
@property
def help_plain(self) -> str:
return markup_as_plain_text(self.help)
@property
def help_md(self) -> str:
return markup_to_markdown(self.help)
@model_validator(mode="before")
@classmethod
def _fmt_metavar(cls, data: Any) -> Any:
if isinstance(data, dict):
metavar = data.get("metavar", "") or data.get( # pyright: ignore[reportUnknownMemberType,reportUnknownVariableType]
"human_readable_name", ""
)
assert isinstance(metavar, str), "metavar must be a string"
metavar = metavar.upper()
if data.get("multiple"): # pyright: ignore[reportUnknownMemberType]
new_metavar = f"<{metavar},[{metavar}...]>"
else:
new_metavar = f"<{metavar}>"
data["metavar"] = new_metavar
return data # pyright: ignore[reportUnknownVariableType]
@computed_field
@property
def show(self) -> bool:
if self.hidden:
return False
if "deprecated" in self.help.lower():
return False
return True
# TODO: split up CommandSummary into CommandSummary and CommandSearchResult
# so that the latter can have the score field
class CommandSummary(BaseModel):
"""Convenience class for accessing information about a command."""
category: Optional[str] = None # not part of TyperCommand
deprecated: bool
epilog: Optional[str]
help: str
hidden: bool
name: str
options_metavar: str
params: List[ParamSummary] = Field([], exclude=True)
score: int = 0 # match score (not part of TyperCommand)
short_help: Optional[str]
@model_validator(mode="before")
@classmethod
def _replace_placeholders(cls, values: Any) -> Any:
"""Replace DefaultPlaceholder values with empty strings."""
if not isinstance(values, dict):
return values
values = cast(Dict[str, Any], values)
for key, value in values.items():
if isinstance(value, DefaultPlaceholder):
# Use its value, otherwise empty string
values[key] = value.value or ""
return values
@classmethod
def from_command(
cls, command: TyperCommand, name: str | None = None, category: str | None = None
) -> CommandSummary:
"""Construct a new CommandSummary from a TyperCommand."""
try:
return cls(
category=category,
deprecated=command.deprecated,
epilog=command.epilog or "",
help=command.help or "",
hidden=command.hidden,
name=name or command.name or "",
options_metavar=command.options_metavar or "",
params=[ParamSummary.from_param(p) for p in command.params],
short_help=command.short_help or "",
)
except ValidationError as e:
raise ZabbixCLIError(
f"Failed to construct command summary for {name or command.name}: {e}"
) from e
@property
def help_plain(self) -> str:
return markup_as_plain_text(self.help)
@property
def help_md(self) -> str:
return markup_to_markdown(self.help)
@computed_field
@property
def usage(self) -> str:
parts = [self.name]
# Assume arg list is sorted by required/optional
# ` [OPTIONAL_ARG1] [OPTIONAL_ARG2]`
for arg in self.arguments:
metavar = arg.metavar or arg.human_readable_name
parts.append(metavar)
# Command with both required and optional options:
# `--option1 --option2 [OPTIONS]`
has_optional = False
for option in self.options:
if option.required:
metavar = option.metavar or option.human_readable_name
if option.opts:
s = f"{max(option.opts)} {metavar}"
else:
# this shouldn't happen, but just in case. A required
# option without any opts is not very useful.
# NOTE: could raise exception here instead
s = metavar
parts.append(s)
else:
has_optional = True
if has_optional:
parts.append("[OPTIONS]")
return " ".join(parts)
@computed_field
@property
def options(self) -> List[ParamSummary]:
return [p for p in self.params if _include_opt(p)]
@computed_field
@property
def arguments(self) -> List[ParamSummary]:
return [p for p in self.params if _include_arg(p)]
def _include_arg(arg: ParamSummary) -> bool:
"""Determine if an argument or option should be included in the help output."""
if not arg.is_argument:
return False
return arg.show
def _include_opt(opt: ParamSummary) -> bool:
"""Determine if an argument or option should be included in the help output."""
if opt.is_argument:
return False
return opt.show
def get_parent_ctx(
ctx: typer.Context | click.core.Context,
) -> typer.Context | click.core.Context:
"""Get the top-level parent context of a context."""
if ctx.parent is None:
return ctx
return get_parent_ctx(ctx.parent)
def get_command_help(command: typer.models.CommandInfo) -> str:
"""Get the help text of a command."""
if command.help:
return command.help
if command.callback and command.callback.__doc__:
lines = command.callback.__doc__.strip().splitlines()
if lines:
return lines[0]
if command.short_help:
return command.short_help
return ""
@lru_cache(maxsize=None)
def get_app_commands(app: typer.Typer) -> list[CommandSummary]:
"""Get a list of commands from a typer app."""
return _get_app_commands(app)
def _get_app_commands(
app: typer.Typer,
cmds: list[CommandSummary] | None = None,
) -> list[CommandSummary]:
if cmds is None:
cmds = []
# NOTE: incorrect type annotation for get_command() here:
# The function can return either a TyperGroup or click.Command
cmd = typer.main.get_command(app)
cmd = cast(Union[TyperGroup, click.Command], cmd)
groups: dict[str, TyperCommand] = {}
try:
groups = cmd.commands # type: ignore
except AttributeError:
pass
# If we have subcommands, we need to go deeper.
for command in groups.values():
if command.deprecated: # skip deprecated commands
continue
category = command.rich_help_panel
# rich_help_panel can also be a DefaultPlaceholder
# even if the type annotation says it's str | None
if category and not isinstance(category, str): # pyright: ignore[reportUnnecessaryIsInstance]
raise ValueError(f"{command.name} is missing a rich_help_panel (category)")
cmds.append(
CommandSummary.from_command(command, name=command.name, category=category)
)
return sorted(cmds, key=lambda x: x.name)
def get_app_callback_options(app: typer.Typer) -> list[typer.models.OptionInfo]:
"""Get the options of the main callback of a Typer app."""
options: List[typer.models.OptionInfo] = []
if not app.registered_callback:
return options
callback = app.registered_callback.callback
if not callback:
return options
if not hasattr(callback, "__defaults__") or not callback.__defaults__:
return options
for option in callback.__defaults__:
options.append(option)
return options
unioslo-zabbix-cli-09a2fab/docs/scripts/utils/markup.py 0000664 0000000 0000000 00000011274 14712653334 0023357 0 ustar 00root root 0000000 0000000 from __future__ import annotations
import itertools
from dataclasses import dataclass
from functools import cmp_to_key
from typing import List
from rich.text import Text
from zabbix_cli.output.style import CodeBlockStyle
from zabbix_cli.output.style import CodeStyle
CODEBLOCK_STYLES = list(CodeBlockStyle)
CODE_STYLES = list(CodeStyle)
CODEBLOCK_LANGS = {
"python": "py",
}
@dataclass
class MarkdownSpan:
start: int
end: int
italic: bool = False
bold: bool = False
code: bool = False
codeblock: bool = False
language: str = ""
def to_symbols(self) -> tuple[MarkdownSymbol, MarkdownSymbol]:
start = MarkdownSymbol.from_span(self, end=False)
end = MarkdownSymbol.from_span(self, end=True)
return start, end
@dataclass
class MarkdownSymbol:
position: int
italic: bool = False
bold: bool = False
code: bool = False
codeblock: bool = False
end: bool = False
language: str = ""
@property
def symbol(self) -> str:
symbol: List[str] = []
if self.codeblock:
# Only insert language when opening codeblock
lang = self.language if not self.end else ""
symbol.append(f"```{lang}\n")
# TODO: add support for language in fences (codeblock)
else:
if self.italic:
symbol.append("*")
if self.bold:
symbol.append("**")
if self.code:
symbol.append("`")
s = "".join(symbol)
if self.end:
s = f"{s[::-1]}"
return s
@classmethod
def from_span(cls, span: MarkdownSpan, end: bool = False) -> MarkdownSymbol:
return cls(
position=span.end if end else span.start,
italic=span.italic,
bold=span.bold,
code=span.code,
codeblock=span.codeblock,
end=end,
language=span.language,
)
# Easier than implementing rich comparison methods on MarkdownSymbol
def mdsymbol_cmp(a: MarkdownSymbol, b: MarkdownSymbol) -> int:
if a.position < b.position:
return -1
elif a.position > b.position:
return 1
else:
# code tags cannot have other tags inside them
if a.code and not b.code:
return 1
if b.code and not a.code:
return -1
return 0
# TODO: rename `markup_to_markdown` to `markup_as_markdown`
# OR rename `markup_to_plaintext` to `markup_as_plaintext`
# I am partial to `x_to_y`.
def markup_to_markdown(s: str) -> str:
"""Parses a string that might contain markup formatting and converts it to Markdown.
This is a very naive implementation that only supports a subset of Rich markup, but it's
good enough for our purposes.
"""
t = Text.from_markup(normalize_spaces(s))
spans: List[MarkdownSpan] = []
# Markdown has more limited styles than Rich markup, so we just
# identify the ones we care about and ignore the rest.
for span in t.spans:
new_span = MarkdownSpan(span.start, span.end)
styles = str(span.style).lower().split(" ")
# Code (block) styles ignore other styles
if any(s in CODEBLOCK_STYLES for s in styles):
new_span.codeblock = True
lang = next((s for s in styles if s in CODEBLOCK_LANGS), "")
new_span.language = CODEBLOCK_LANGS.get(lang, "")
elif any(s in CODE_STYLES for s in styles):
new_span.code = True
else:
if "italic" in styles:
new_span.italic = True
if "bold" in styles:
new_span.bold = True
spans.append(new_span)
# Convert MarkdownSpans to MarkdownSymbols
# Each MarkdownSymbol represents a markdown formatting character along
# with its position in the string.
symbols = list(itertools.chain.from_iterable(sp.to_symbols() for sp in spans))
symbols = sorted(symbols, key=cmp_to_key(mdsymbol_cmp))
# List of characters that make up string
plaintext = list(str(t.plain.strip())) # remove leading and trailing whitespace
offset = 0
for symbol in symbols:
plaintext.insert(symbol.position + offset, symbol.symbol)
offset += 1
return "".join(plaintext)
def normalize_spaces(s: str) -> str:
"""Normalizes spaces in a string while keeping newlines intact."""
split = filter(None, s.split(" "))
parts: List[str] = []
for part in split:
if part.endswith("\n"):
parts.append(part)
else:
parts.append(f"{part} ")
return "".join(parts)
def markup_as_plain_text(s: str) -> str:
"""Renders a string that might contain markup formatting as a plain text string."""
return Text.from_markup(s).plain
unioslo-zabbix-cli-09a2fab/docs/static/ 0000775 0000000 0000000 00000000000 14712653334 0020141 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/static/img/ 0000775 0000000 0000000 00000000000 14712653334 0020715 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/static/img/plugins/ 0000775 0000000 0000000 00000000000 14712653334 0022376 5 ustar 00root root 0000000 0000000 unioslo-zabbix-cli-09a2fab/docs/static/img/plugins/console01.png 0000664 0000000 0000000 00000070051 14712653334 0024712 0 ustar 00root root 0000000 0000000 PNG
IHDR f 8 @iCCPICC Profile HWXS[@h)H6B AŎ,*TD(v(XPPł]y+ߛ;Μ;s NpDlTa8:ȏL' p01##j^ j/g-<~. $T^.7q5W$(ͦ牤V%BXZSx&6q J*8 ːsӡj?ĎB@bZ 1b|A|8paY1
q!$Z9|a\OF+Krrmx^Fl=HFdP
%Qao d͆U!;ÄLy
Y`b ǽqO<^}au<: ]S⟢~"?.ա2{a>гdYY4vdG2JA%[?[bY$v;5:vkڱR