From b874e7e66fd49e1c242600442ca9533ca59f1061 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Fri, 18 Oct 2024 17:57:37 -0400 Subject: [PATCH 01/42] zabbix hook added --- borgmatic/config/schema.yaml | 82 +++++++++++++++++++++++++ borgmatic/hooks/dispatch.py | 2 + borgmatic/hooks/monitor.py | 1 + borgmatic/hooks/zabbix.py | 93 +++++++++++++++++++++++++++++ docs/how-to/monitor-your-backups.md | 50 ++++++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 borgmatic/hooks/zabbix.py diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 847ca6f5..09b6847b 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1602,6 +1602,88 @@ properties: example: - start - finish + zabbix: + type: object + additionalProperties: false + properties: + itemid: + type: integer + description: | + The itemid to publish to + for details. + example: 55105 + host: + type: string + description: | + Host name where the item is stored. + Required if 'itemid' is not set + example: borg-server + key: + type: string + description: | + Key of the host where the item is stored. + Required if 'itemid' is not set + example: borg.status + server: + type: string + description: | + The address of your self-hosted zabbix instance. + example: https://zabbix.your-domain.com + username: + type: string + description: | + The username used for authentication. + example: testuser + password: + type: string + description: | + The password used for authentication. + example: fakepassword + api_key: + type: string + description: | + The api_key used for authentication. + example: fakekey + start: + type: object + properties: + value: + type: string + description: | + The string to set the item value to on start. + example: STARTED + finish: + type: object + properties: + value: + type: string + description: | + The string to set the item value to on finish. + example: FINISH + fail: + type: object + properties: + value: + type: string + description: | + The string to set the item value to on fail. + example: ERROR + states: + type: array + items: + type: string + enum: + - start + - finish + - fail + uniqueItems: true + description: | + List of one or more monitoring states to ping for: "start", + "finish", and/or "fail". Defaults to pinging for failure + only. + example: + - start + - finish apprise: type: object required: ['services'] diff --git a/borgmatic/hooks/dispatch.py b/borgmatic/hooks/dispatch.py index 6131ef6b..be85af8c 100644 --- a/borgmatic/hooks/dispatch.py +++ b/borgmatic/hooks/dispatch.py @@ -14,6 +14,7 @@ from borgmatic.hooks import ( postgresql, sqlite, uptimekuma, + zabbix, ) logger = logging.getLogger(__name__) @@ -32,6 +33,7 @@ HOOK_NAME_TO_MODULE = { 'postgresql_databases': postgresql, 'sqlite_databases': sqlite, 'uptime_kuma': uptimekuma, + 'zabbix': zabbix, } diff --git a/borgmatic/hooks/monitor.py b/borgmatic/hooks/monitor.py index 9512fe99..de4951b7 100644 --- a/borgmatic/hooks/monitor.py +++ b/borgmatic/hooks/monitor.py @@ -9,6 +9,7 @@ MONITOR_HOOK_NAMES = ( 'ntfy', 'pagerduty', 'uptime_kuma', + 'zabbix', ) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py new file mode 100644 index 00000000..ea17d7a0 --- /dev/null +++ b/borgmatic/hooks/zabbix.py @@ -0,0 +1,93 @@ +import logging +import requests +import json + +logger = logging.getLogger(__name__) + + +def initialize_monitor( + ping_url, config, config_filename, monitoring_log_level, dry_run +): # pragma: no cover + ''' + No initialization is necessary for this monitor. + ''' + pass + + +def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run): + ''' + Ping the configured Zabbix topic. Use the given configuration filename in any log entries. + If this is a dry run, then don't actually ping anything. + ''' + + run_states = hook_config.get('states', ['fail']) + + if state.name.lower() in run_states: + dry_run_label = ' (dry run; not actually pinging)' if dry_run else '' + + state_config = hook_config.get(state.name.lower(),{'value': f'invalid',},) + + base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php') + username = hook_config.get('username') + password = hook_config.get('password') + api_key = hook_config.get('api_key') + itemid = hook_config.get('itemid') + host = hook_config.get('host') + key = hook_config.get('key') + + value = state_config.get('value') + + headers = {'Content-Type': 'application/json-rpc'} + + logger.info(f'{config_filename}: Pinging zabbix {dry_run_label}') + logger.debug(f'{config_filename}: Using zabbix ping URL {base_url}') + + # Determine the zabbix method used to store the value: itemid or host/key + if (itemid) is not None: + logger.info(f'{config_filename}: Updating {itemid} on zabbix') + data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1} + + elif (host and key) is not None: + logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on zabbix') + data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1} + + elif (host) is not None: + logger.warning( f'{config_filename}: Key missing for zabbix authentication' ) + + elif (key) is not None: + logger.warning( f'{config_filename}: Host missing for zabbix authentication' ) + + # Determine the authentication method: API key or username/password + auth = None + if (api_key) is not None: + logger.info(f'{config_filename}: Using API key auth for zabbix') + headers['Authorization'] = 'Bearer ' + api_key + + elif (username and password) is not None: + logger.info(f'{config_filename}: Using user/pass auth with user {username} for zabbix') + response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') + data['auth'] = response.json().get('result') + + elif username is not None: + logger.warning( f'{config_filename}: Password missing for zabbix authentication, defaulting to no auth' ) + + elif password is not None: + logger.warning( f'{config_filename}: Username missing for zabbix authentication, defaulting to no auth' ) + + if not dry_run: + logging.getLogger('urllib3').setLevel(logging.ERROR) + try: + response = requests.post(base_url, headers=headers, data=json.dumps(data)) + if not response.ok: + response.raise_for_status() + except requests.exceptions.RequestException as error: + logger.warning(f'{config_filename}: zabbix error: {error}') + + +def destroy_monitor( + ping_url_or_uuid, config, config_filename, monitoring_log_level, dry_run +): # pragma: no cover + ''' + No destruction is necessary for this monitor. + ''' + pass diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index e96c4040..ddc540b9 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -47,6 +47,7 @@ them as backups happen: * [ntfy](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#ntfy-hook) * [PagerDuty](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#pagerduty-hook) * [Uptime Kuma](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#uptime-kuma-hook) + * [Zabbix](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/#zabbix-hook) The idea is that you'll receive an alert when something goes wrong or when the service doesn't hear from borgmatic for a configured interval (if supported). @@ -562,6 +563,55 @@ Heartbeat Retry = 360 # = 10 minutes Resend Notification every X times = 1 ``` +## zabbix hook + +New in version 1.8.15 +[zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time. + +This hook does not do any notifications on it's own. Instead, it relies on +your zabbix instance to notify and perform escalations based on the zabbix +configuration. The `states` list will choose which states to trigger on. +Each state can have its own custom values configured. These values are +populated in the item data in zabbix. If none are provided, will use the +default. + +An example configuration is shown here with all the available options. + +```yaml +zabbix: + server: http://cloud.zabbix.com/zabbix/api_jsonrpc.php + + username: myuser + password: secret + api_key: b2ecba64d8beb47fc161ae48b164cfd7104a79e8e48e6074ef5b141d8a0aeeca + + host: "borg-server" + key: borg.status + itemid: 55105 + + start: + value: "STARTED" + finish: + value: "OK" + fail: + value: "ERROR" + states: + - start + - finish + - fail +``` + +Authentication Methods +Authentication can be accomplished via `api_key` or `username` and `password`. +If both are declared, `api_key` will be chosen. + +Items The item +to be updated can be chosen by either declaring the `itemid` or +`host` and `key`. If both are declared, `itemid` will be chosen. + +Keep in mind that `host` the Host name on the host and not the +Visual name. + ## Scripting borgmatic From 3da7471fe6dd9ddcd267349ee1fe982852ccbf32 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Fri, 18 Oct 2024 18:00:14 -0400 Subject: [PATCH 02/42] added note about zabbix 7.0+ --- docs/how-to/monitor-your-backups.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index ddc540b9..3bc1a056 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -601,6 +601,9 @@ zabbix: - fail ``` +Zabbix 7.0+ +This hook requires the Zabbix server be running version 7.0+ + Authentication Methods Authentication can be accomplished via `api_key` or `username` and `password`. If both are declared, `api_key` will be chosen. From fc87b74ab033c9b495e5d87b330943051bc15d22 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 16:52:45 -0400 Subject: [PATCH 03/42] capital fixed --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 3bc1a056..2a5b821f 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -563,7 +563,7 @@ Heartbeat Retry = 360 # = 10 minutes Resend Notification every X times = 1 ``` -## zabbix hook +## Zabbix hook New in version 1.8.15 [zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time. From 9abc5c60d48fa9ddb6fc90a5bd465acbdec4cbdb Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 16:53:18 -0400 Subject: [PATCH 04/42] its --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 2a5b821f..65c6fed7 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -568,7 +568,7 @@ Resend Notification every X times = 1 New in version 1.8.15 [zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time. -This hook does not do any notifications on it's own. Instead, it relies on +This hook does not do any notifications on its own. Instead, it relies on your zabbix instance to notify and perform escalations based on the zabbix configuration. The `states` list will choose which states to trigger on. Each state can have its own custom values configured. These values are From 1a8e8835c123a6d1f11c293e7a35432b88b54d07 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:00:31 -0400 Subject: [PATCH 05/42] added quotes and period for host description --- borgmatic/config/schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 09b6847b..b44d5263 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1616,7 +1616,7 @@ properties: type: string description: | Host name where the item is stored. - Required if 'itemid' is not set + Required if "itemid" is not set. example: borg-server key: type: string From 702e55e6f7976fd7e2acaa383c526f386ff84757 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:08:37 -0400 Subject: [PATCH 06/42] formatting fix for key --- borgmatic/config/schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index b44d5263..eec2de12 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1622,7 +1622,7 @@ properties: type: string description: | Key of the host where the item is stored. - Required if 'itemid' is not set + Required if "itemid" is not set. example: borg.status server: type: string From 80839566d688ab422a3dcb04590949f4636fdc56 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:12:18 -0400 Subject: [PATCH 07/42] better wording for API key --- borgmatic/config/schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index eec2de12..4499c4f6 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1642,7 +1642,7 @@ properties: api_key: type: string description: | - The api_key used for authentication. + The API key used for authentication. example: fakekey start: type: object From 42fb8c38e00431f5d1227ed263800b5900e29db1 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:15:55 -0400 Subject: [PATCH 08/42] better comments and logging --- borgmatic/hooks/zabbix.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index ea17d7a0..89d7306b 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -16,14 +16,14 @@ def initialize_monitor( def ping_monitor(hook_config, config, config_filename, state, monitoring_log_level, dry_run): ''' - Ping the configured Zabbix topic. Use the given configuration filename in any log entries. - If this is a dry run, then don't actually ping anything. + Update the configured Zabbix item using either the itemid, or a host and key. + If this is a dry run, then don't actually update anything. ''' run_states = hook_config.get('states', ['fail']) if state.name.lower() in run_states: - dry_run_label = ' (dry run; not actually pinging)' if dry_run else '' + dry_run_label = ' (dry run; not actually updating)' if dry_run else '' state_config = hook_config.get(state.name.lower(),{'value': f'invalid',},) @@ -39,8 +39,8 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev headers = {'Content-Type': 'application/json-rpc'} - logger.info(f'{config_filename}: Pinging zabbix {dry_run_label}') - logger.debug(f'{config_filename}: Using zabbix ping URL {base_url}') + logger.info(f'{config_filename}: Updating zabbix {dry_run_label}') + logger.debug(f'{config_filename}: Using zabbix URL: {base_url}') # Determine the zabbix method used to store the value: itemid or host/key if (itemid) is not None: From 5a464b318616b987a69781a0d887a24010eb4030 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:19:51 -0400 Subject: [PATCH 09/42] better logic for run_states --- borgmatic/hooks/zabbix.py | 102 +++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 89d7306b..8951a995 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -22,66 +22,68 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev run_states = hook_config.get('states', ['fail']) - if state.name.lower() in run_states: - dry_run_label = ' (dry run; not actually updating)' if dry_run else '' + if state.name.lower() not in run_states: + return - state_config = hook_config.get(state.name.lower(),{'value': f'invalid',},) + dry_run_label = ' (dry run; not actually updating)' if dry_run else '' - base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php') - username = hook_config.get('username') - password = hook_config.get('password') - api_key = hook_config.get('api_key') - itemid = hook_config.get('itemid') - host = hook_config.get('host') - key = hook_config.get('key') + state_config = hook_config.get(state.name.lower(),{'value': f'invalid',},) - value = state_config.get('value') + base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php') + username = hook_config.get('username') + password = hook_config.get('password') + api_key = hook_config.get('api_key') + itemid = hook_config.get('itemid') + host = hook_config.get('host') + key = hook_config.get('key') - headers = {'Content-Type': 'application/json-rpc'} + value = state_config.get('value') - logger.info(f'{config_filename}: Updating zabbix {dry_run_label}') - logger.debug(f'{config_filename}: Using zabbix URL: {base_url}') + headers = {'Content-Type': 'application/json-rpc'} - # Determine the zabbix method used to store the value: itemid or host/key - if (itemid) is not None: - logger.info(f'{config_filename}: Updating {itemid} on zabbix') - data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1} + logger.info(f'{config_filename}: Updating zabbix {dry_run_label}') + logger.debug(f'{config_filename}: Using zabbix URL: {base_url}') + + # Determine the zabbix method used to store the value: itemid or host/key + if (itemid) is not None: + logger.info(f'{config_filename}: Updating {itemid} on zabbix') + data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1} + + elif (host and key) is not None: + logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on zabbix') + data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1} + + elif (host) is not None: + logger.warning( f'{config_filename}: Key missing for zabbix authentication' ) + + elif (key) is not None: + logger.warning( f'{config_filename}: Host missing for zabbix authentication' ) + + # Determine the authentication method: API key or username/password + auth = None + if (api_key) is not None: + logger.info(f'{config_filename}: Using API key auth for zabbix') + headers['Authorization'] = 'Bearer ' + api_key - elif (host and key) is not None: - logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on zabbix') - data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1} + elif (username and password) is not None: + logger.info(f'{config_filename}: Using user/pass auth with user {username} for zabbix') + response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') + data['auth'] = response.json().get('result') - elif (host) is not None: - logger.warning( f'{config_filename}: Key missing for zabbix authentication' ) + elif username is not None: + logger.warning( f'{config_filename}: Password missing for zabbix authentication, defaulting to no auth' ) - elif (key) is not None: - logger.warning( f'{config_filename}: Host missing for zabbix authentication' ) + elif password is not None: + logger.warning( f'{config_filename}: Username missing for zabbix authentication, defaulting to no auth' ) - # Determine the authentication method: API key or username/password - auth = None - if (api_key) is not None: - logger.info(f'{config_filename}: Using API key auth for zabbix') - headers['Authorization'] = 'Bearer ' + api_key - - elif (username and password) is not None: - logger.info(f'{config_filename}: Using user/pass auth with user {username} for zabbix') - response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') - data['auth'] = response.json().get('result') - - elif username is not None: - logger.warning( f'{config_filename}: Password missing for zabbix authentication, defaulting to no auth' ) - - elif password is not None: - logger.warning( f'{config_filename}: Username missing for zabbix authentication, defaulting to no auth' ) - - if not dry_run: - logging.getLogger('urllib3').setLevel(logging.ERROR) - try: - response = requests.post(base_url, headers=headers, data=json.dumps(data)) - if not response.ok: - response.raise_for_status() - except requests.exceptions.RequestException as error: - logger.warning(f'{config_filename}: zabbix error: {error}') + if not dry_run: + logging.getLogger('urllib3').setLevel(logging.ERROR) + try: + response = requests.post(base_url, headers=headers, data=json.dumps(data)) + if not response.ok: + response.raise_for_status() + except requests.exceptions.RequestException as error: + logger.warning(f'{config_filename}: zabbix error: {error}') def destroy_monitor( From 5da898003f99f25815123bcf4e093bbe7f9e9684 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:21:30 -0400 Subject: [PATCH 10/42] removed unnecessary parentheses here and below --- borgmatic/hooks/zabbix.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 8951a995..56118939 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -45,27 +45,27 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev logger.debug(f'{config_filename}: Using zabbix URL: {base_url}') # Determine the zabbix method used to store the value: itemid or host/key - if (itemid) is not None: + if itemid is not None: logger.info(f'{config_filename}: Updating {itemid} on zabbix') data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1} - elif (host and key) is not None: + elif host and key is not None: logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on zabbix') data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1} - elif (host) is not None: + elif host is not None: logger.warning( f'{config_filename}: Key missing for zabbix authentication' ) - elif (key) is not None: + elif key is not None: logger.warning( f'{config_filename}: Host missing for zabbix authentication' ) # Determine the authentication method: API key or username/password auth = None - if (api_key) is not None: + if api_key is not None: logger.info(f'{config_filename}: Using API key auth for zabbix') headers['Authorization'] = 'Bearer ' + api_key - elif (username and password) is not None: + elif username and password is not None: logger.info(f'{config_filename}: Using user/pass auth with user {username} for zabbix') response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') data['auth'] = response.json().get('result') From f616284ffbae19ce9605fe40c09379b4bc7eb2ab Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:24:20 -0400 Subject: [PATCH 11/42] better description for server string --- borgmatic/config/schema.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 4499c4f6..134e265b 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1628,6 +1628,7 @@ properties: type: string description: | The address of your self-hosted zabbix instance. + Not specifying a server will default to the Zabbix cloud. example: https://zabbix.your-domain.com username: type: string From 38ce98771bd7a86697dec9b5573fc3466b4a1d3d Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:28:54 -0400 Subject: [PATCH 12/42] removed spaces before logging --- borgmatic/hooks/zabbix.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 56118939..0e2c38db 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -36,9 +36,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev itemid = hook_config.get('itemid') host = hook_config.get('host') key = hook_config.get('key') - value = state_config.get('value') - headers = {'Content-Type': 'application/json-rpc'} logger.info(f'{config_filename}: Updating zabbix {dry_run_label}') From 9b2ac961d7d0c8ccdd4905f2d1f275c222659b51 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:30:28 -0400 Subject: [PATCH 13/42] capitalized Zabbix in logging --- borgmatic/hooks/zabbix.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 0e2c38db..101b5711 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -39,40 +39,40 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev value = state_config.get('value') headers = {'Content-Type': 'application/json-rpc'} - logger.info(f'{config_filename}: Updating zabbix {dry_run_label}') - logger.debug(f'{config_filename}: Using zabbix URL: {base_url}') + logger.info(f'{config_filename}: Updating Zabbix {dry_run_label}') + logger.debug(f'{config_filename}: Using Zabbix URL: {base_url}') # Determine the zabbix method used to store the value: itemid or host/key if itemid is not None: - logger.info(f'{config_filename}: Updating {itemid} on zabbix') + logger.info(f'{config_filename}: Updating {itemid} on Zabbix') data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1} elif host and key is not None: - logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on zabbix') + logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on Zabbix') data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1} elif host is not None: - logger.warning( f'{config_filename}: Key missing for zabbix authentication' ) + logger.warning( f'{config_filename}: Key missing for Zabbix authentication' ) elif key is not None: - logger.warning( f'{config_filename}: Host missing for zabbix authentication' ) + logger.warning( f'{config_filename}: Host missing for Zabbix authentication' ) # Determine the authentication method: API key or username/password auth = None if api_key is not None: - logger.info(f'{config_filename}: Using API key auth for zabbix') + logger.info(f'{config_filename}: Using API key auth for Zabbix') headers['Authorization'] = 'Bearer ' + api_key elif username and password is not None: - logger.info(f'{config_filename}: Using user/pass auth with user {username} for zabbix') + logger.info(f'{config_filename}: Using user/pass auth with user {username} for Zabbix') response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') data['auth'] = response.json().get('result') elif username is not None: - logger.warning( f'{config_filename}: Password missing for zabbix authentication, defaulting to no auth' ) + logger.warning( f'{config_filename}: Password missing for Zabbix authentication, defaulting to no auth' ) elif password is not None: - logger.warning( f'{config_filename}: Username missing for zabbix authentication, defaulting to no auth' ) + logger.warning( f'{config_filename}: Username missing for Zabbix authentication, defaulting to no auth' ) if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) @@ -81,7 +81,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: - logger.warning(f'{config_filename}: zabbix error: {error}') + logger.warning(f'{config_filename}: Zabbix error: {error}') def destroy_monitor( From b5fb0c8247aba96112a75954f1b4db1754379f30 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:33:09 -0400 Subject: [PATCH 14/42] added returns when expected values are not provided --- borgmatic/hooks/zabbix.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 101b5711..ffcd05ee 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -53,9 +53,11 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev elif host is not None: logger.warning( f'{config_filename}: Key missing for Zabbix authentication' ) + return elif key is not None: logger.warning( f'{config_filename}: Host missing for Zabbix authentication' ) + return # Determine the authentication method: API key or username/password auth = None @@ -69,10 +71,12 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev data['auth'] = response.json().get('result') elif username is not None: - logger.warning( f'{config_filename}: Password missing for Zabbix authentication, defaulting to no auth' ) + logger.warning( f'{config_filename}: Password missing for Zabbix authentication' ) + return elif password is not None: - logger.warning( f'{config_filename}: Username missing for Zabbix authentication, defaulting to no auth' ) + logger.warning( f'{config_filename}: Username missing for Zabbix authentication' ) + return if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) From f2d7687ca3ed42a5663bebbd818df6a8eca4be44 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:34:21 -0400 Subject: [PATCH 15/42] removed unused variable --- borgmatic/hooks/zabbix.py | 1 - 1 file changed, 1 deletion(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index ffcd05ee..4676968c 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -60,7 +60,6 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev return # Determine the authentication method: API key or username/password - auth = None if api_key is not None: logger.info(f'{config_filename}: Using API key auth for Zabbix') headers['Authorization'] = 'Bearer ' + api_key From 8edb40a8e97e40765b3b0ea1be6a1c79d5cc3edd Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:38:24 -0400 Subject: [PATCH 16/42] added dryrun check --- borgmatic/hooks/zabbix.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 4676968c..d3802757 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -66,8 +66,9 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev elif username and password is not None: logger.info(f'{config_filename}: Using user/pass auth with user {username} for Zabbix') - response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') - data['auth'] = response.json().get('result') + if not dry_run: + response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') + data['auth'] = response.json().get('result') elif username is not None: logger.warning( f'{config_filename}: Password missing for Zabbix authentication' ) From 78f81c7b738affee762a8aafb7df38d2f526bf79 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:39:36 -0400 Subject: [PATCH 17/42] changed verison to 1.9 - zabbix hook --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 65c6fed7..8791ffba 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -565,7 +565,7 @@ Resend Notification every X times = 1 ## Zabbix hook -New in version 1.8.15 +New in version 1.9 [zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time. This hook does not do any notifications on its own. Instead, it relies on From 02a219fac297ea273a89f21db7365b26923cd854 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:40:24 -0400 Subject: [PATCH 18/42] it will --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 8791ffba..9095e569 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -572,7 +572,7 @@ This hook does not do any notifications on its own. Instead, it relies on your zabbix instance to notify and perform escalations based on the zabbix configuration. The `states` list will choose which states to trigger on. Each state can have its own custom values configured. These values are -populated in the item data in zabbix. If none are provided, will use the +populated in the item data in zabbix. If none are provided, it will use the default. An example configuration is shown here with all the available options. From c1a08edca23a4f60512eeabfb1a1274ff6a78ea1 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:42:11 -0400 Subject: [PATCH 19/42] subheader formatting --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 9095e569..4323f8da 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -601,7 +601,7 @@ zabbix: - fail ``` -Zabbix 7.0+ +### Zabbix 7.0+ This hook requires the Zabbix server be running version 7.0+ Authentication Methods From d6e1cc3e12ddcee0bb80c50b563e566fc228dbec Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:43:54 -0400 Subject: [PATCH 20/42] better wording --- docs/how-to/monitor-your-backups.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 4323f8da..a0550a2c 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -612,8 +612,8 @@ If both are declared, `api_key` will be chosen. to be updated can be chosen by either declaring the `itemid` or `host` and `key`. If both are declared, `itemid` will be chosen. -Keep in mind that `host` the Host name on the host and not the -Visual name. +Keep in mind that `host` is referring to the 'Host name' on the +Zabbix host and not the 'Visual name'. ## Scripting borgmatic From 7aff22536df75998f174b83aa16066e87465f66c Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 17:53:07 -0400 Subject: [PATCH 21/42] better itemid description in schema --- borgmatic/config/schema.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 134e265b..7739a53a 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1609,8 +1609,8 @@ properties: itemid: type: integer description: | - The itemid to publish to - for details. + The ID of the Zabbix item used for collecting data. + Unique across the entire Zabbix system. example: 55105 host: type: string From 1b9f95ca471c1598780a55a2f362d22b4fc9692f Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 18:04:28 -0400 Subject: [PATCH 22/42] better description in schema for server --- borgmatic/config/schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 7739a53a..6a535545 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1627,7 +1627,7 @@ properties: server: type: string description: | - The address of your self-hosted zabbix instance. + The address of your zabbix instance. Not specifying a server will default to the Zabbix cloud. example: https://zabbix.your-domain.com username: From 2877c1ad0d635324753a5ac056b9854f9dbf4d6a Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 18:10:58 -0400 Subject: [PATCH 23/42] added spaces after commas --- borgmatic/hooks/zabbix.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index d3802757..774565ed 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -27,7 +27,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev dry_run_label = ' (dry run; not actually updating)' if dry_run else '' - state_config = hook_config.get(state.name.lower(),{'value': f'invalid',},) + state_config = hook_config.get(state.name.lower(), {'value': f'invalid',},) base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php') username = hook_config.get('username') @@ -45,11 +45,11 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev # Determine the zabbix method used to store the value: itemid or host/key if itemid is not None: logger.info(f'{config_filename}: Updating {itemid} on Zabbix') - data = {"jsonrpc":"2.0","method":"history.push","params":{"itemid":itemid,"value":value},"id":1} + data = {"jsonrpc":"2.0", "method":"history.push", "params":{"itemid":itemid, "value":value}, "id":1} elif host and key is not None: logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on Zabbix') - data = {"jsonrpc":"2.0","method":"history.push","params":{"host":host,"key":key,"value":value},"id":1} + data = {"jsonrpc":"2.0", "method":"history.push", "params":{"host":host, "key":key,"value":value}, "id":1} elif host is not None: logger.warning( f'{config_filename}: Key missing for Zabbix authentication' ) From b42793a2dc8a3b8b60c3c02ea78924c17955cc21 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Wed, 23 Oct 2024 18:26:22 -0400 Subject: [PATCH 24/42] convert concat to fstring --- borgmatic/hooks/zabbix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 774565ed..b33ce752 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -67,7 +67,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev elif username and password is not None: logger.info(f'{config_filename}: Using user/pass auth with user {username} for Zabbix') if not dry_run: - response = requests.post(base_url, headers=headers, data='{"jsonrpc":"2.0","method":"user.login","params":{"username":"'+username+'","password":"'+password+'"},"id":1}') + response = requests.post(base_url, headers=headers, data=f'{{"jsonrpc":"2.0","method":"user.login","params":{{"username":"{username}","password":"{password}"}},"id":1}}') data['auth'] = response.json().get('result') elif username is not None: From 9ba8ca24ebc301c2915f7a0fb67bbb210b8cef89 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Thu, 24 Oct 2024 07:52:32 -0400 Subject: [PATCH 25/42] changes made with 'tox -e black' --- borgmatic/hooks/zabbix.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index b33ce752..5649775b 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -27,7 +27,12 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev dry_run_label = ' (dry run; not actually updating)' if dry_run else '' - state_config = hook_config.get(state.name.lower(), {'value': f'invalid',},) + state_config = hook_config.get( + state.name.lower(), + { + 'value': f'invalid', + }, + ) base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php') username = hook_config.get('username') @@ -45,37 +50,51 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev # Determine the zabbix method used to store the value: itemid or host/key if itemid is not None: logger.info(f'{config_filename}: Updating {itemid} on Zabbix') - data = {"jsonrpc":"2.0", "method":"history.push", "params":{"itemid":itemid, "value":value}, "id":1} - + data = { + "jsonrpc": "2.0", + "method": "history.push", + "params": {"itemid": itemid, "value": value}, + "id": 1, + } + elif host and key is not None: logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on Zabbix') - data = {"jsonrpc":"2.0", "method":"history.push", "params":{"host":host, "key":key,"value":value}, "id":1} + data = { + "jsonrpc": "2.0", + "method": "history.push", + "params": {"host": host, "key": key, "value": value}, + "id": 1, + } elif host is not None: - logger.warning( f'{config_filename}: Key missing for Zabbix authentication' ) + logger.warning(f'{config_filename}: Key missing for Zabbix authentication') return elif key is not None: - logger.warning( f'{config_filename}: Host missing for Zabbix authentication' ) + logger.warning(f'{config_filename}: Host missing for Zabbix authentication') return # Determine the authentication method: API key or username/password if api_key is not None: logger.info(f'{config_filename}: Using API key auth for Zabbix') headers['Authorization'] = 'Bearer ' + api_key - + elif username and password is not None: logger.info(f'{config_filename}: Using user/pass auth with user {username} for Zabbix') if not dry_run: - response = requests.post(base_url, headers=headers, data=f'{{"jsonrpc":"2.0","method":"user.login","params":{{"username":"{username}","password":"{password}"}},"id":1}}') + response = requests.post( + base_url, + headers=headers, + data=f'{{"jsonrpc":"2.0","method":"user.login","params":{{"username":"{username}","password":"{password}"}},"id":1}}', + ) data['auth'] = response.json().get('result') elif username is not None: - logger.warning( f'{config_filename}: Password missing for Zabbix authentication' ) + logger.warning(f'{config_filename}: Password missing for Zabbix authentication') return elif password is not None: - logger.warning( f'{config_filename}: Username missing for Zabbix authentication' ) + logger.warning(f'{config_filename}: Username missing for Zabbix authentication') return if not dry_run: From f18219a7689d19dd219d980de38e23e7d883a441 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Thu, 24 Oct 2024 08:00:14 -0400 Subject: [PATCH 26/42] changes made with 'tox - e isort' --- borgmatic/hooks/zabbix.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 5649775b..743376ce 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -1,6 +1,7 @@ -import logging -import requests import json +import logging + +import requests logger = logging.getLogger(__name__) From ccbf668bea9c840d2f402a52a9cf65cdf69319fc Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Thu, 24 Oct 2024 09:01:10 -0400 Subject: [PATCH 27/42] fix for if logic --- borgmatic/hooks/zabbix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 743376ce..ef16633a 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -58,7 +58,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev "id": 1, } - elif host and key is not None: + elif (host and key) is not None: logger.info(f'{config_filename}: Updating Host:{host} and Key:{key} on Zabbix') data = { "jsonrpc": "2.0", @@ -80,7 +80,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev logger.info(f'{config_filename}: Using API key auth for Zabbix') headers['Authorization'] = 'Bearer ' + api_key - elif username and password is not None: + elif (username and password) is not None: logger.info(f'{config_filename}: Using user/pass auth with user {username} for Zabbix') if not dry_run: response = requests.post( From 90ccbecf077a912f1de1e6ad81a1460cde8e7822 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Fri, 25 Oct 2024 08:08:38 -0400 Subject: [PATCH 28/42] better documatation description --- docs/how-to/monitor-your-backups.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index a0550a2c..56dd2c61 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -569,11 +569,11 @@ Resend Notification every X times = 1 [zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time. This hook does not do any notifications on its own. Instead, it relies on -your zabbix instance to notify and perform escalations based on the zabbix -configuration. The `states` list will choose which states to trigger on. -Each state can have its own custom values configured. These values are -populated in the item data in zabbix. If none are provided, it will use the -default. +your Zabbix instance to notify and perform escalations based on the Zabbix +configuration. The `states` defined in the configuration will determine which states +will trigger the hook. The value defined in the configuration of each state is +used to populate the data of the configured Zabbix item. If none are provided, +it will use the default. An example configuration is shown here with all the available options. From cda83310c87e894703c20baddb35324dff22c8f5 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Fri, 25 Oct 2024 08:10:25 -0400 Subject: [PATCH 29/42] fixed version number --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 56dd2c61..25ed6d36 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -565,7 +565,7 @@ Resend Notification every X times = 1 ## Zabbix hook -New in version 1.9 +New in version 1.9.0 [zabbix](https://www.zabbix.com/) is an open-source monitoring tool used for tracking and managing the performance and availability of networks, servers, and applications in real-time. This hook does not do any notifications on its own. Instead, it relies on From 11fee81486f98cfd21684ebbd0537f709fc8f05b Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Fri, 25 Oct 2024 17:28:02 -0400 Subject: [PATCH 30/42] converted string to dictionary as requested --- borgmatic/hooks/zabbix.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index ef16633a..a682eaac 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -82,12 +82,17 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev elif (username and password) is not None: logger.info(f'{config_filename}: Using user/pass auth with user {username} for Zabbix') + auth_data = { + "jsonrpc": "2.0", + "method": "user.login", + "params": { + "username": username, + "password": password + }, + "id": 1 + } if not dry_run: - response = requests.post( - base_url, - headers=headers, - data=f'{{"jsonrpc":"2.0","method":"user.login","params":{{"username":"{username}","password":"{password}"}},"id":1}}', - ) + response = requests.post(base_url, headers=headers, json=auth_data) data['auth'] = response.json().get('result') elif username is not None: From ffb431e3ab9a6adb1f8a95660430c8fdc8a487eb Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Fri, 25 Oct 2024 17:34:33 -0400 Subject: [PATCH 31/42] minor improvement --- borgmatic/hooks/zabbix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index a682eaac..2e5c2c2f 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -106,7 +106,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.post(base_url, headers=headers, data=json.dumps(data)) + response = requests.post(base_url, headers=headers, json=data) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: From 385ef2d0129fd4ee969cd0ce090c7f03b81f1a84 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:36:30 -0400 Subject: [PATCH 32/42] fixes and first unit test attempt --- borgmatic/config/schema.yaml | 1 - borgmatic/hooks/zabbix.py | 33 ++++-- tests/unit/hooks/test_zabbix.py | 194 ++++++++++++++++++++++++++++++++ 3 files changed, 219 insertions(+), 9 deletions(-) create mode 100644 tests/unit/hooks/test_zabbix.py diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 6a535545..1a1f7fc3 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1628,7 +1628,6 @@ properties: type: string description: | The address of your zabbix instance. - Not specifying a server will default to the Zabbix cloud. example: https://zabbix.your-domain.com username: type: string diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 2e5c2c2f..021bd4ef 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -31,11 +31,11 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev state_config = hook_config.get( state.name.lower(), { - 'value': f'invalid', + 'value': state.name.lower(), }, ) - base_url = hook_config.get('server', 'https://cloud.zabbix.com/zabbix/api_jsonrpc.php') + server = hook_config.get('server') username = hook_config.get('username') password = hook_config.get('password') api_key = hook_config.get('api_key') @@ -46,7 +46,11 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev headers = {'Content-Type': 'application/json-rpc'} logger.info(f'{config_filename}: Updating Zabbix {dry_run_label}') - logger.debug(f'{config_filename}: Using Zabbix URL: {base_url}') + logger.debug(f'{config_filename}: Using Zabbix URL: {server}') + + if server is None: + logger.warning(f'{config_filename}: Server missing for Zabbix') + return # Determine the zabbix method used to store the value: itemid or host/key if itemid is not None: @@ -68,11 +72,14 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev } elif host is not None: - logger.warning(f'{config_filename}: Key missing for Zabbix authentication') + logger.warning(f'{config_filename}: Key missing for Zabbix') return elif key is not None: - logger.warning(f'{config_filename}: Host missing for Zabbix authentication') + logger.warning(f'{config_filename}: Host missing for Zabbix.') + return + else: + logger.warning(f'{config_filename}: No zabbix itemid or host/key provided.') return # Determine the authentication method: API key or username/password @@ -92,8 +99,14 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev "id": 1 } if not dry_run: - response = requests.post(base_url, headers=headers, json=auth_data) - data['auth'] = response.json().get('result') + logging.getLogger('urllib3').setLevel(logging.ERROR) + try: + response = requests.post(server, headers=headers, json=auth_data) + data['auth'] = response.json().get('result') + if not response.ok: + response.raise_for_status() + except requests.exceptions.RequestException as error: + logger.warning(f'{config_filename}: Zabbix error: {error}') elif username is not None: logger.warning(f'{config_filename}: Password missing for Zabbix authentication') @@ -102,11 +115,15 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev elif password is not None: logger.warning(f'{config_filename}: Username missing for Zabbix authentication') return + else: + logger.warning(f'{config_filename}: Authentication data missing for Zabbix') + return + if not dry_run: logging.getLogger('urllib3').setLevel(logging.ERROR) try: - response = requests.post(base_url, headers=headers, json=data) + response = requests.post(server, headers=headers, json=data) if not response.ok: response.raise_for_status() except requests.exceptions.RequestException as error: diff --git a/tests/unit/hooks/test_zabbix.py b/tests/unit/hooks/test_zabbix.py new file mode 100644 index 00000000..ce9aaa48 --- /dev/null +++ b/tests/unit/hooks/test_zabbix.py @@ -0,0 +1,194 @@ +from enum import Enum + +from flexmock import flexmock + +import borgmatic.hooks.monitor +from borgmatic.hooks import zabbix as module + +server = 'https://zabbix.com/zabbix/api_jsonrpc.php' +itemid = 55105 +username = 'testuser' +password = 'fakepassword' +api_key = 'fakekey' +host = 'borg-server' +key = 'borg.status' +value = 'fail' + +data_host_key = { + "jsonrpc": "2.0", + "method": "history.push", + "params": {"host": host, "key": key, "value": value}, + "id": 1, +} + +data_itemid = { + "jsonrpc": "2.0", + "method": "history.push", + "params": {"itemid": itemid, "value": value}, + "id": 1, +} + +data_user_login = { + "jsonrpc": "2.0", + "method": "user.login", + "params": {"username": username, "password": password}, + "id": 1, +} + +auth_headers_api_key = { + 'Content-Type': 'application/json-rpc', + 'Authorization': f'Bearer {api_key}' +} + +auth_headers_username_password = { + 'Content-Type': 'application/json-rpc' +} + +def test_ping_monitor_config_with_api_key_only(): + hook_config = { + 'api_key': api_key + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_with_host_only(): + hook_config = { + 'host': host + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_with_key_only(): + hook_config = { + 'key': key + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_with_server_only(): + hook_config = { + 'server': server + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_user_password_no_zabbix_data(): + hook_config = { + 'server': server, + 'username': username, + 'password': password + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_api_key_no_zabbix_data(): + hook_config = { + 'server': server, + 'api_key': api_key + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_itemid_no_auth_data(): + hook_config = { + 'server': server, + 'itemid': itemid + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_host_and_key_no_auth_data(): + hook_config = { + 'server': server, + 'host': host, + 'key': key + } + flexmock(module.logger).should_receive('warning').once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_host_and_key_with_api_key_auth_data(): + hook_config = { + 'server': server, + 'host': host, + 'key': key, + 'api_key': api_key + } + flexmock(module.requests).should_receive('post').with_args( + f'{server}', + headers=auth_headers_api_key, + json=data_host_key, + ).and_return(flexmock(ok=True)).once() + flexmock(module.logger).should_receive('warning').never() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) \ No newline at end of file From d060f8d77a2ab339a80563f845189f5d79187bfa Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:38:17 -0400 Subject: [PATCH 33/42] fix log for dry_run_label --- borgmatic/hooks/zabbix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/hooks/zabbix.py b/borgmatic/hooks/zabbix.py index 021bd4ef..13f51b64 100644 --- a/borgmatic/hooks/zabbix.py +++ b/borgmatic/hooks/zabbix.py @@ -45,7 +45,7 @@ def ping_monitor(hook_config, config, config_filename, state, monitoring_log_lev value = state_config.get('value') headers = {'Content-Type': 'application/json-rpc'} - logger.info(f'{config_filename}: Updating Zabbix {dry_run_label}') + logger.info(f'{config_filename}: Updating Zabbix{dry_run_label}') logger.debug(f'{config_filename}: Using Zabbix URL: {server}') if server is None: From 237999cc81cb1b35dab251d4154cf9aeeb8ecc7c Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:40:14 -0400 Subject: [PATCH 34/42] fix word-wrap --- borgmatic/config/schema.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 1a1f7fc3..884fc6ff 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1615,14 +1615,12 @@ properties: host: type: string description: | - Host name where the item is stored. - Required if "itemid" is not set. + Host name where the item is stored. Required if "itemid" is not set. example: borg-server key: type: string description: | - Key of the host where the item is stored. - Required if "itemid" is not set. + Key of the host where the item is stored. Required if "itemid" is not set. example: borg.status server: type: string From d11c517f675cf35894b33432d22cdbd9e6d36302 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:41:34 -0400 Subject: [PATCH 35/42] better schema descriptions --- borgmatic/config/schema.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 884fc6ff..b00641f0 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1630,17 +1630,17 @@ properties: username: type: string description: | - The username used for authentication. + The username used for authentication. Not needed if using an API key. example: testuser password: type: string description: | - The password used for authentication. + The password used for authentication. Not needed if using an API key. example: fakepassword api_key: type: string description: | - The API key used for authentication. + The API key used for authentication. Not needed if using an username/password. example: fakekey start: type: object From 60c5949c23cc6eb66f88d6d4d9b118ee43fde78e Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:51:08 -0400 Subject: [PATCH 36/42] adjusted to accept integers and strings --- borgmatic/config/schema.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index b00641f0..72e7e1f3 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1646,25 +1646,25 @@ properties: type: object properties: value: - type: string + type: ["integer", "string"] description: | - The string to set the item value to on start. + The value to set the item to on start. example: STARTED finish: type: object properties: value: - type: string + type: ["integer", "string"] description: | - The string to set the item value to on finish. + The value to set the item to on finish. example: FINISH fail: type: object properties: value: - type: string + type: ["integer", "string"] description: | - The string to set the item value to on fail. + The value to set the item to on fail. example: ERROR states: type: array From f758374772f71d721d3db379b756febb83a8b476 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:52:20 -0400 Subject: [PATCH 37/42] adjustment in docs to reflect changes in 385ef2d --- docs/how-to/monitor-your-backups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/monitor-your-backups.md b/docs/how-to/monitor-your-backups.md index 25ed6d36..3ea51735 100644 --- a/docs/how-to/monitor-your-backups.md +++ b/docs/how-to/monitor-your-backups.md @@ -573,7 +573,7 @@ your Zabbix instance to notify and perform escalations based on the Zabbix configuration. The `states` defined in the configuration will determine which states will trigger the hook. The value defined in the configuration of each state is used to populate the data of the configured Zabbix item. If none are provided, -it will use the default. +it default to a lower-case string of the state. An example configuration is shown here with all the available options. From 52fbf8cb2416d24b9781b55b4214727a8b58241d Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Mon, 28 Oct 2024 09:54:07 -0400 Subject: [PATCH 38/42] capital --- borgmatic/config/schema.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/borgmatic/config/schema.yaml b/borgmatic/config/schema.yaml index 72e7e1f3..e4a672c4 100644 --- a/borgmatic/config/schema.yaml +++ b/borgmatic/config/schema.yaml @@ -1625,7 +1625,7 @@ properties: server: type: string description: | - The address of your zabbix instance. + The address of your Zabbix instance. example: https://zabbix.your-domain.com username: type: string From 87d824553d0780e85a6303cf287c0cb71f4e012c Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Tue, 29 Oct 2024 08:05:05 -0400 Subject: [PATCH 39/42] converted constants to capitals --- tests/unit/hooks/test_zabbix.py | 76 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/unit/hooks/test_zabbix.py b/tests/unit/hooks/test_zabbix.py index ce9aaa48..21d23083 100644 --- a/tests/unit/hooks/test_zabbix.py +++ b/tests/unit/hooks/test_zabbix.py @@ -5,48 +5,48 @@ from flexmock import flexmock import borgmatic.hooks.monitor from borgmatic.hooks import zabbix as module -server = 'https://zabbix.com/zabbix/api_jsonrpc.php' -itemid = 55105 -username = 'testuser' -password = 'fakepassword' -api_key = 'fakekey' -host = 'borg-server' -key = 'borg.status' -value = 'fail' +SERVER = 'https://zabbix.com/zabbix/api_jsonrpc.php' +ITEMID = 55105 +USERNAME = 'testuser' +PASSWORD = 'fakepassword' +API_KEY = 'fakekey' +HOST = 'borg-server' +KEY = 'borg.status' +VALUE = 'fail' -data_host_key = { +DATA_HOST_KEY = { "jsonrpc": "2.0", "method": "history.push", - "params": {"host": host, "key": key, "value": value}, + "params": {"host": HOST, "key": KEY, "value": VALUE}, "id": 1, } -data_itemid = { +DATA_ITEMID = { "jsonrpc": "2.0", "method": "history.push", - "params": {"itemid": itemid, "value": value}, + "params": {"itemid": ITEMID, "value": VALUE}, "id": 1, } -data_user_login = { +DATA_USER_LOGIN = { "jsonrpc": "2.0", "method": "user.login", - "params": {"username": username, "password": password}, + "params": {"username": USERNAME, "password": PASSWORD}, "id": 1, } -auth_headers_api_key = { +AUTH_HEADERS_API_KEY = { 'Content-Type': 'application/json-rpc', - 'Authorization': f'Bearer {api_key}' + 'Authorization': f'Bearer {API_KEY}' } -auth_headers_username_password = { +AUTH_HEADERS_USERNAME_PASSWORD = { 'Content-Type': 'application/json-rpc' } def test_ping_monitor_config_with_api_key_only(): hook_config = { - 'api_key': api_key + 'api_key': API_KEY } flexmock(module.logger).should_receive('warning').once() @@ -61,7 +61,7 @@ def test_ping_monitor_config_with_api_key_only(): def test_ping_monitor_config_with_host_only(): hook_config = { - 'host': host + 'host': HOST } flexmock(module.logger).should_receive('warning').once() @@ -76,7 +76,7 @@ def test_ping_monitor_config_with_host_only(): def test_ping_monitor_config_with_key_only(): hook_config = { - 'key': key + 'key': KEY } flexmock(module.logger).should_receive('warning').once() @@ -91,7 +91,7 @@ def test_ping_monitor_config_with_key_only(): def test_ping_monitor_config_with_server_only(): hook_config = { - 'server': server + 'server': SERVER } flexmock(module.logger).should_receive('warning').once() @@ -106,9 +106,9 @@ def test_ping_monitor_config_with_server_only(): def test_ping_monitor_config_user_password_no_zabbix_data(): hook_config = { - 'server': server, - 'username': username, - 'password': password + 'server': SERVER, + 'username': USERNAME, + 'password': PASSWORD } flexmock(module.logger).should_receive('warning').once() @@ -123,8 +123,8 @@ def test_ping_monitor_config_user_password_no_zabbix_data(): def test_ping_monitor_config_api_key_no_zabbix_data(): hook_config = { - 'server': server, - 'api_key': api_key + 'server': SERVER, + 'api_key': API_KEY } flexmock(module.logger).should_receive('warning').once() @@ -139,8 +139,8 @@ def test_ping_monitor_config_api_key_no_zabbix_data(): def test_ping_monitor_config_itemid_no_auth_data(): hook_config = { - 'server': server, - 'itemid': itemid + 'server': SERVER, + 'itemid': ITEMID } flexmock(module.logger).should_receive('warning').once() @@ -155,9 +155,9 @@ def test_ping_monitor_config_itemid_no_auth_data(): def test_ping_monitor_config_host_and_key_no_auth_data(): hook_config = { - 'server': server, - 'host': host, - 'key': key + 'server': SERVER, + 'host': HOST, + 'key': KEY } flexmock(module.logger).should_receive('warning').once() @@ -172,15 +172,15 @@ def test_ping_monitor_config_host_and_key_no_auth_data(): def test_ping_monitor_config_host_and_key_with_api_key_auth_data(): hook_config = { - 'server': server, - 'host': host, - 'key': key, - 'api_key': api_key + 'server': SERVER, + 'host': HOST, + 'key': KEY, + 'api_key': API_KEY } flexmock(module.requests).should_receive('post').with_args( - f'{server}', - headers=auth_headers_api_key, - json=data_host_key, + f'{SERVER}', + headers=AUTH_HEADERS_API_KEY, + json=DATA_HOST_KEY, ).and_return(flexmock(ok=True)).once() flexmock(module.logger).should_receive('warning').never() From f713f1df7eaf800bc9943723a9d309872d1e721c Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Tue, 29 Oct 2024 08:21:52 -0400 Subject: [PATCH 40/42] better function names and comments --- tests/unit/hooks/test_zabbix.py | 34 ++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/tests/unit/hooks/test_zabbix.py b/tests/unit/hooks/test_zabbix.py index 21d23083..7647b80c 100644 --- a/tests/unit/hooks/test_zabbix.py +++ b/tests/unit/hooks/test_zabbix.py @@ -44,7 +44,9 @@ AUTH_HEADERS_USERNAME_PASSWORD = { 'Content-Type': 'application/json-rpc' } -def test_ping_monitor_config_with_api_key_only(): +def test_ping_monitor_config_with_api_key_only_exit_early(): + # This test should exit early since only providing an API KEY is not enough + # for the hook to work hook_config = { 'api_key': API_KEY } @@ -59,7 +61,9 @@ def test_ping_monitor_config_with_api_key_only(): dry_run=False, ) -def test_ping_monitor_config_with_host_only(): +def test_ping_monitor_config_with_host_only_exit_early(): + # This test should exit early since only providing a HOST is not enough + # for the hook to work hook_config = { 'host': HOST } @@ -74,7 +78,9 @@ def test_ping_monitor_config_with_host_only(): dry_run=False, ) -def test_ping_monitor_config_with_key_only(): +def test_ping_monitor_config_with_key_only_exit_early(): + # This test should exit early since only providing a KEY is not enough + # for the hook to work hook_config = { 'key': KEY } @@ -89,7 +95,9 @@ def test_ping_monitor_config_with_key_only(): dry_run=False, ) -def test_ping_monitor_config_with_server_only(): +def test_ping_monitor_config_with_server_only_exit_early(): + # This test should exit early since only providing a SERVER is not enough + # for the hook to work hook_config = { 'server': SERVER } @@ -104,7 +112,8 @@ def test_ping_monitor_config_with_server_only(): dry_run=False, ) -def test_ping_monitor_config_user_password_no_zabbix_data(): +def test_ping_monitor_config_user_password_no_zabbix_data_exit_early(): + # This test should exit early since there are HOST/KEY or ITEMID provided to publish data to hook_config = { 'server': SERVER, 'username': USERNAME, @@ -121,7 +130,8 @@ def test_ping_monitor_config_user_password_no_zabbix_data(): dry_run=False, ) -def test_ping_monitor_config_api_key_no_zabbix_data(): +def test_ping_monitor_config_api_key_no_zabbix_data_exit_early(): + # This test should exit early since there are HOST/KEY or ITEMID provided to publish data to hook_config = { 'server': SERVER, 'api_key': API_KEY @@ -137,7 +147,9 @@ def test_ping_monitor_config_api_key_no_zabbix_data(): dry_run=False, ) -def test_ping_monitor_config_itemid_no_auth_data(): +def test_ping_monitor_config_itemid_no_auth_data_exit_early(): + # This test should exit early since there is no authentication provided + # and Zabbix requires authentication to use it's API hook_config = { 'server': SERVER, 'itemid': ITEMID @@ -153,7 +165,9 @@ def test_ping_monitor_config_itemid_no_auth_data(): dry_run=False, ) -def test_ping_monitor_config_host_and_key_no_auth_data(): +def test_ping_monitor_config_host_and_key_no_auth_data_exit_early(): + # This test should exit early since there is no authentication provided + # and Zabbix requires authentication to use it's API hook_config = { 'server': SERVER, 'host': HOST, @@ -170,7 +184,9 @@ def test_ping_monitor_config_host_and_key_no_auth_data(): dry_run=False, ) -def test_ping_monitor_config_host_and_key_with_api_key_auth_data(): +def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful(): + # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY + # to authenticate and HOST/KEY to know which item to populate in Zabbix. hook_config = { 'server': SERVER, 'host': HOST, From e52e29444f04e4708692e2da4a5687069562d508 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Tue, 29 Oct 2024 08:54:25 -0400 Subject: [PATCH 41/42] added user/pass auth test --- tests/unit/hooks/test_zabbix.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/unit/hooks/test_zabbix.py b/tests/unit/hooks/test_zabbix.py index 7647b80c..5e3c7afd 100644 --- a/tests/unit/hooks/test_zabbix.py +++ b/tests/unit/hooks/test_zabbix.py @@ -21,6 +21,14 @@ DATA_HOST_KEY = { "id": 1, } +DATA_HOST_KEY_WITH_TOKEN = { + "jsonrpc": "2.0", + "method": "history.push", + "params": {"host": HOST, "key": KEY, "value": VALUE}, + "id": 1, + "auth": "3fe6ed01a69ebd79907a120bcd04e494" +} + DATA_ITEMID = { "jsonrpc": "2.0", "method": "history.push", @@ -200,6 +208,43 @@ def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful(): ).and_return(flexmock(ok=True)).once() flexmock(module.logger).should_receive('warning').never() + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_host_and_key_with_username_password_auth_data_successful(): + # This test should simulate a successful POST to a Zabbix server. This test uses USERNAME/PASSWORD + # to authenticate and HOST/KEY to know which item to populate in Zabbix. + hook_config = { + 'server': SERVER, + 'host': HOST, + 'key': KEY, + 'username': USERNAME, + 'password': PASSWORD + } + + auth_response = flexmock(ok=True) + auth_response.should_receive('json').and_return({"jsonrpc":"2.0","result":"3fe6ed01a69ebd79907a120bcd04e494","id":1}) + + flexmock(module.requests).should_receive('post').with_args( + f'{SERVER}', + headers=AUTH_HEADERS_USERNAME_PASSWORD, + json=DATA_USER_LOGIN, + ).and_return(auth_response).once() + + flexmock(module.logger).should_receive('warning').never() + + flexmock(module.requests).should_receive('post').with_args( + f'{SERVER}', + headers=AUTH_HEADERS_USERNAME_PASSWORD, + json=DATA_HOST_KEY_WITH_TOKEN, + ).and_return(flexmock(ok=True)).once() + module.ping_monitor( hook_config, {}, From fa6a4734d4375c01cb0a61315db7874145afd993 Mon Sep 17 00:00:00 2001 From: Tony Fernandez Date: Tue, 29 Oct 2024 09:00:06 -0400 Subject: [PATCH 42/42] added auth tests with item id --- tests/unit/hooks/test_zabbix.py | 71 ++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/unit/hooks/test_zabbix.py b/tests/unit/hooks/test_zabbix.py index 5e3c7afd..2fe28cca 100644 --- a/tests/unit/hooks/test_zabbix.py +++ b/tests/unit/hooks/test_zabbix.py @@ -36,6 +36,14 @@ DATA_ITEMID = { "id": 1, } +DATA_HOST_KEY_WITH_TOKEN = { + "jsonrpc": "2.0", + "method": "history.push", + "params": {"itemid": ITEMID, "value": VALUE}, + "id": 1, + "auth": "3fe6ed01a69ebd79907a120bcd04e494" +} + DATA_USER_LOGIN = { "jsonrpc": "2.0", "method": "user.login", @@ -252,4 +260,65 @@ def test_ping_monitor_config_host_and_key_with_username_password_auth_data_succe borgmatic.hooks.monitor.State.FAIL, monitoring_log_level=1, dry_run=False, - ) \ No newline at end of file + ) + +def test_ping_monitor_config_itemid_with_api_key_auth_data_successful(): + # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY + # to authenticate and HOST/KEY to know which item to populate in Zabbix. + hook_config = { + 'server': SERVER, + 'itemid': ITEMID, + 'api_key': API_KEY + } + flexmock(module.requests).should_receive('post').with_args( + f'{SERVER}', + headers=AUTH_HEADERS_API_KEY, + json=DATA_ITEMID, + ).and_return(flexmock(ok=True)).once() + flexmock(module.logger).should_receive('warning').never() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) + +def test_ping_monitor_config_itemid_with_username_password_auth_data_successful(): + # This test should simulate a successful POST to a Zabbix server. This test uses USERNAME/PASSWORD + # to authenticate and HOST/KEY to know which item to populate in Zabbix. + hook_config = { + 'server': SERVER, + 'itemid': ITEMID, + 'username': USERNAME, + 'password': PASSWORD + } + + auth_response = flexmock(ok=True) + auth_response.should_receive('json').and_return({"jsonrpc":"2.0","result":"3fe6ed01a69ebd79907a120bcd04e494","id":1}) + + flexmock(module.requests).should_receive('post').with_args( + f'{SERVER}', + headers=AUTH_HEADERS_USERNAME_PASSWORD, + json=DATA_USER_LOGIN, + ).and_return(auth_response).once() + + flexmock(module.logger).should_receive('warning').never() + + flexmock(module.requests).should_receive('post').with_args( + f'{SERVER}', + headers=AUTH_HEADERS_USERNAME_PASSWORD, + json=DATA_HOST_KEY_WITH_TOKEN, + ).and_return(flexmock(ok=True)).once() + + module.ping_monitor( + hook_config, + {}, + 'config.yaml', + borgmatic.hooks.monitor.State.FAIL, + monitoring_log_level=1, + dry_run=False, + ) +test_ping_monitor_config_itemid_with_username_password_auth_data_successful() \ No newline at end of file