Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package monitoring-plugins-http_json for 
openSUSE:Factory checked in at 2026-05-05 15:15:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/monitoring-plugins-http_json (Old)
 and      /work/SRC/openSUSE:Factory/.monitoring-plugins-http_json.new.30200 
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "monitoring-plugins-http_json"

Tue May  5 15:15:12 2026 rev:7 rq:1350730 version:2.4.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/monitoring-plugins-http_json/monitoring-plugins-http_json.changes
        2026-03-02 17:35:47.343963810 +0100
+++ 
/work/SRC/openSUSE:Factory/.monitoring-plugins-http_json.new.30200/monitoring-plugins-http_json.changes
     2026-05-05 15:16:31.962032487 +0200
@@ -1,0 +2,8 @@
+Mon May  4 08:43:15 UTC 2026 - Martin Hauke <[email protected]>
+
+- Update to version 2.4.0
+  * Add default timeout and handle timeout error.
+  * Add -M flag to map non-numeric perfdata values.
+  * Allow for bracket in key.
+
+-------------------------------------------------------------------

Old:
----
  nagios-http-json-2.3.1.tar.gz

New:
----
  nagios-http-json-2.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ monitoring-plugins-http_json.spec ++++++
--- /var/tmp/diff_new_pack.zBxxCW/_old  2026-05-05 15:16:32.446052569 +0200
+++ /var/tmp/diff_new_pack.zBxxCW/_new  2026-05-05 15:16:32.450052735 +0200
@@ -19,7 +19,7 @@
 %define modname monitoring-plugins-http_json
 %define pythons python3
 Name:           monitoring-plugins-http_json
-Version:        2.3.1
+Version:        2.4.0
 Release:        0
 Summary:        Plugin for Nagios which checks json values from a given HTTP 
endpoint
 License:        Apache-2.0

++++++ nagios-http-json-2.3.1.tar.gz -> nagios-http-json-2.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/README.md 
new/nagios-http-json-2.4.0/README.md
--- old/nagios-http-json-2.3.1/README.md        2026-02-25 08:29:54.000000000 
+0100
+++ new/nagios-http-json-2.4.0/README.md        2026-05-04 08:54:24.000000000 
+0200
@@ -1,8 +1,8 @@
 ![CI](https://github.com/drewkerrigan/nagios-http-json/workflows/CI/badge.svg)
 
-# Nagios Json Plugin
+# Nagios JSON Plugin
 
-This is a generic plugin for Nagios which checks json values from a given HTTP 
endpoint against argument specified rules and determines the status and 
performance data for that service.
+This is a generic plugin for Nagios which checks JSON values from a given HTTP 
endpoint against argument specified rules and determines the status and 
performance data for that service.
 
 ## Installation
 
@@ -22,34 +22,30 @@
 Add the following service definition to your server config (`localhost.cfg`):
 
 ```
-
 define service {
         use                             local-service
         host_name                       localhost
         service_description             <command_description>
         check_command                   <command_name>
-        }
-
+}
 ```
 
 Add the following command definition to your commands config 
(`commands.config`):
 
 ```
-
-define command{
+define command {
         command_name    <command_name>
         command_line    /usr/bin/python 
/usr/local/nagios/libexec/plugins/check_http_json.py -H <host>:<port> -p <path> 
[-e|-q|-w|-c <rules>] [-m <metrics>]
-        }
-
+}
 ```
 
-### Icinga2
+### Icinga
 
-An example Icinga2 command definition can be found here: 
(`contrib/icinga2_check_command_definition.conf`)
+An example Icinga command definition can be found here: [contrib](contrib/).
 
 ## Usage
 
-Executing `./check_http_json.py -h` will yield the following details:
+Executing `check_http_json.py -h` will yield the following details:
 
 ```
 usage: check_http_json.py [-h] [-d] [-s] -H HOST [-k] [-V] [--cacert CACERT]
@@ -66,25 +62,24 @@
                           [-y [KEY_VALUE_LIST_NOT [KEY_VALUE_LIST_NOT ...]]]
                           [-Y [KEY_VALUE_LIST_NOT_CRITICAL 
[KEY_VALUE_LIST_NOT_CRITICAL ...]]]
                           [-m [METRIC_LIST [METRIC_LIST ...]]]
+                          [-M KEY=VALUE]
 
 Check HTTP JSON Nagios Plugin
 
-Generic Nagios plugin which checks json values from a given endpoint against
+Generic Nagios plugin which checks JSON values from a given endpoint against
 argument specified rules and determines the status and performance data for
 that service.
 
-Version: 2.2.0 (2024-05-14)
-
 options:
-  -h, --help            show this help message and exit
-  -d, --debug           debug mode
+  -h, --help            Show this help message and exit
+  -d, --debug           Debug mode
   -v, --verbose         Verbose mode. Multiple -v options increase the 
verbosity
   -s, --ssl             use TLS to connect to remote host
-  -H HOST, --host HOST  remote host to query
-  -k, --insecure        do not check server SSL certificate
+  -H HOST, --host HOST  Remote host to query
+  -k, --insecure        Do not check server SSL certificate
   -X {GET,POST}, --request {GET,POST}
                         Specifies a custom request method to use when 
communicating with the HTTP server
-  -V, --version         print version of this plugin
+  -V, --version         Print version of this plugin
   --cacert CACERT       SSL CA certificate
   --cert CERT           SSL client certificate
   --key KEY             SSL client key ( if not bundled into the cert )
@@ -98,9 +93,9 @@
                         Exit with specified code when no valid JSON is 
returned. Examples: 1 for Warning, 2 for Critical, 3 for Unknown (default: 3)
   -B AUTH, --basic-auth AUTH
                         Basic auth string "username:password"
-  -D DATA, --data DATA  The http payload to send as a POST
+  -D DATA, --data DATA  The HTTP payload to send as a POST
   -A HEADERS, --headers HEADERS
-                        The http headers in JSON format.
+                        The HTTP headers in JSON format.
   -f SEPARATOR, --field_separator SEPARATOR
                         JSON Field separator, defaults to "."; Select element 
in an array with "(" ")"
   -F VALUE_SEPARATOR, --value_separator VALUE_SEPARATOR
@@ -125,7 +120,7 @@
                         (key[>alias],value key2,value2) to determine status.
                         Multiple key values can be delimited with colon
                         (key,value1:value2). Return warning if the key is older
-                        than the value (ex.: 30s,10m,2h,3d,...). 
+                        than the value (ex.: 30s,10m,2h,3d,...).
                         With at it return warning if the key is jounger
                         than the value (ex.: @30s,@10m,@2h,@3d,...).
                         With Minus you can shift the time in the future.
@@ -141,9 +136,11 @@
                         Same as -q but return critical if equality check 
succeeds.
   -m [METRIC_LIST ...], --key_metric [METRIC_LIST ...]
                         Gathers the values of these keys (key[>alias], 
UnitOfMeasure,WarnRange,CriticalRange,Min,Max) for Nagios
-                        performance data. More information about Range format 
and units of measure for nagios can be found at nagios-
+                        performance data. More information about Range format 
and units of measure for Nagios can be found at nagios-
                         plugins.org/doc/guidelines.html Additional formats for 
this parameter are: (key[>alias]),
                         (key[>alias],UnitOfMeasure), 
(key[>alias],UnitOfMeasure,WarnRange, CriticalRange).
+  -M KEY=VALUE          Map the values of the gathered metric to the given 
values. This can be used to map non-numeric values to numeric values, e.g. -M
+                        Up=1. Can used multiple times. This flag is meant to 
be used with the -m flag.
 ```
 
 The check plugin respects the environment variables `HTTP_PROXY`, 
`HTTPS_PROXY`.
@@ -154,129 +151,193 @@
 
 **Data for key** `value`:
 
-    { "value": 1000 }
+```json
+{ "value": 1000 }
+```
 
 **Data for key** `capacity.value`:
 
-    {
-        "capacity": {
-            "value": 1000
-        }
+```json
+{
+    "capacity": {
+        "value": 1000
     }
+}
+```
 
 **Data for key** `(0).capacity.value`:
 
-    [
-        {
-            "capacity": {
-                "value": 1000
-            }
+```json
+[
+    {
+        "capacity": {
+            "value": 1000
         }
-    ]
+    }
+]
+```
 
 **Data for keys of all items in a list** `(*).capacity.value`:
 
-    [
-        {
-            "capacity": {
-                "value": 1000
-            }
-        },
-        {
-            "capacity": {
-                "value": 2200
-            }
+```json
+[
+    {
+        "capacity": {
+            "value": 1000
         }
-    ]
+    },
+    {
+        "capacity": {
+            "value": 2200
+        }
+    }
+]
+```
 
 **Data for separator** `-f _` **and key** 
`(0)_gauges_jvm.buffers.direct.capacity_value`:
 
-    [
-        {
-            "gauges": {
-                "jvm.buffers.direct.capacity":
-                    "value": 1000
-                }
+```json
+[
+    {
+      "gauges": {
+            "jvm.buffers.direct.capacity": {
+                "value": 1000
             }
         }
-    ]
+    }
+]
+```
 
 **Data for keys** `ring_members(0)`, `ring_members(1)`, `ring_members(2)`:
 
-    {
-        "ring_members": [
-            "[email protected]",
-            "[email protected]",
-            "[email protected]"
-        ]
-    }
-
+```json
+{
+    "ring_members": [
+        "[email protected]",
+        "[email protected]",
+        "[email protected]"
+    ]
+}
+```
 
 **Data for multiple keys for an object** `-q capacity1.value,True 
capacity2.value,True capacity3.value,True`
 
-    {
-      "capacity1": {
-        "value": true
-      },
-      "capacity2": {
-        "value": true
-      },
-      "capacity3": {
-        "value": true
-      }
-    }
-
+```json
+{
+  "capacity1": {
+    "value": true
+  },
+  "capacity2": {
+    "value": true
+  },
+  "capacity3": {
+    "value": true
+  }
+}
+```
 
 ### Thresholds and Ranges
 
 **Data**:
 
-    { "metric": 1000 }
+```json
+{ "metric": 1000 }
+```
 
 #### Relevant Commands
 
-* **Warning:** `./check_http_json.py -H <host>:<port> -p <path> -w 
"metric,RANGE"`
-* **Critical:** `./check_http_json.py -H <host>:<port> -p <path> -c 
"metric,RANGE"`
-* **Metrics with Warning:** `./check_http_json.py -H <host>:<port> -p <path> 
-w "metric,RANGE"`
-* **Metrics with Critical:**
+**Warning:**
 
-        ./check_http_json.py -H <host>:<port> -p <path> -w "metric,,,RANGE"
-        ./check_http_json.py -H <host>:<port> -p <path> -w "metric,,,,MIN,MAX"
+```bash
+check_http_json.py -H <host>:<port> -p <path> -w "metric,RANGE"
+```
+
+**Critical:**
+
+```bash
+check_http_json.py -H <host>:<port> -p <path> -c "metric,RANGE"
+```
+
+**Metrics with Warning:**
+
+```bash
+check_http_json.py -H <host>:<port> -p <path> -w "metric,RANGE"
+```
+
+**Metrics with Critical:**
+
+```bash
+check_http_json.py -H <host>:<port> -p <path> -w "metric,,,RANGE"
+check_http_json.py -H <host>:<port> -p <path> -w "metric,,,,MIN,MAX"
+```
 
 #### Range Definitions
 
-* **Format:** [@]START:END
-* **Generates a Warning or Critical if...**
-    * **Value is less than 0 or greater than 1000:** `1000` or `0:1000`
-    * **Value is greater than or equal to 1000, or less than or equal to 0:** 
`@1000` or `@0:1000`
-    * **Value is less than 1000:** `1000:`
-    * **Value is greater than 1000:** `~:1000`
-    * **Value is greater than or equal to 1000:** `@1000:`
+* Format: [@]START:END
+* Generates a Warning or Critical if:
+    * Value is less than 0 or greater than 1000: `1000` or `0:1000`
+    * Value is greater than or equal to 1000, or less than or equal to 0: 
`@1000` or `@0:1000`
+    * Value is less than 1000: `1000:`
+    * Value is greater than 1000: `~:1000`
+    * Value is greater than or equal to 1000: `@1000:`
+
+More info about Nagios Range format and Units of Measure can be found at 
[https://www.monitoring-plugins.org/doc/guidelines.html](https://www.monitoring-plugins.org/doc/guidelines.html).
 
-More info about Nagios Range format and Units of Measure can be found at 
[https://nagios-plugins.org/doc/guidelines.html](https://nagios-plugins.org/doc/guidelines.html).
+### Performance Data Metrics
+
+The `-m` and `-M` flags can be used to generate performance data from the JSON 
data.
+
+```bash
+check_http_json.py -H host.internal --path example-data.json -e 
'service.requests' -m 'service.requests'
+OK: 'service.requests'=12  Status OK. |'service.requests'=12
+
+check_http_json.py -H host.internal --path example-data.json -e 
'service.requests' -m 'service.requests>webshopRequests,c'
+OK: 'webshopRequests'=12c  Status OK. |'webshopRequests'=12c
+```
+
+The `-M` flag can be used to map non-numeric values to numeric values.
+
+```bash
+check_http_json.py -H host.internal --path example-data.json -e 
'service.status' -m 'service.status'
+OK: 'service.status'=Up  Status OK. |'service.requests'=Up
+
+check_http_json.py -H host.internal --path example-data.json -e 
'service.status' -m 'service.status' -M Up=1
+OK: 'service.status'=1  Status OK. |'service.requests'=1
+```
 
 ### Timestamp
 
 **Data**:
 
-    { "metric": "2020-01-01 10:10:00.000000+00:00" }
+```json
+{ "metric": "2020-01-01 10:10:00.000000+00:00" }
+```
 
 #### Relevant Commands
 
-* **Warning:** `./check_http_json.py -H <host>:<port> -p <path> --key_time 
"metric,TIME"`
-* **Critical:** `./check_http_json.py -H <host>:<port> -p <path> 
--key_time_critical "metric,TIME"`
+**Warning:**
+
+```bash
+check_http_json.py -H <host>:<port> -p <path> --key_time "metric,TIME"
+```
+
+**Critical:**
+
+```bash
+check_http_json.py -H <host>:<port> -p <path> --key_time_critical "metric,TIME"
+```
 
 #### TIME Definitions
 
-* **Format:** [@][-]TIME
-* **Generates a Warning or Critical if...**
-    * **Timestamp is more than 30 seconds in the past:** `30s`
-    * **Timestamp is more than 5 minutes in the past:** `5m`
-    * **Timestamp is more than 12 hours in the past:** `12h`
-    * **Timestamp is more than 2 days in the past:** `2d`
-    * **Timestamp is more than 30 minutes in the future:** `-30m`
-    * **Timestamp is not more than 30 minutes in the future:** `@-30m`
-    * **Timestamp is not more than 30 minutes in the past:** `@30m`
+* Format: [@][-]TIME
+* Generates a Warning or Critical if:
+    * Timestamp is more than 30 seconds in the past: `30s`
+    * Timestamp is more than 5 minutes in the past: `5m`
+    * Timestamp is more than 12 hours in the past: `12h`
+    * Timestamp is more than 2 days in the past: `2d`
+    * Timestamp is more than 30 minutes in the future: `-30m`
+    * Timestamp is not more than 30 minutes in the future: `@-30m`
+    * Timestamp is not more than 30 minutes in the past: `@30m`
 
 ##### Timestamp Format
 
@@ -296,7 +357,9 @@
 
 #### Using Headers
 
-* `./check_http_json.py -H <host>:<port> -p <path> -A '{"content-type": 
"application/json"}' -w "metric,RANGE"`
+```
+check_http_json.py -H <host>:<port> -p <path> -A '{"content-type": 
"application/json"}' -w "metric,RANGE"
+```
 
 ## License
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/check_http_json.py 
new/nagios-http-json-2.4.0/check_http_json.py
--- old/nagios-http-json-2.3.1/check_http_json.py       2026-02-25 
08:29:54.000000000 +0100
+++ new/nagios-http-json-2.4.0/check_http_json.py       2026-05-04 
08:54:24.000000000 +0200
@@ -15,7 +15,7 @@
 """
 Check HTTP JSON Nagios Plugin
 
-Generic Nagios plugin which checks json values from a given endpoint against
+Generic check plugin which checks JSON values from a given endpoint against
 argument specified rules and determines the status and performance data for
 that service.
 """
@@ -25,8 +25,8 @@
 CRITICAL_CODE = 2
 UNKNOWN_CODE = 3
 
-__version__ = '2.3.1'
-__version_date__ = '2026-02-25'
+__version__ = '2.4.0'
+__version_date__ = '2026-05-04'
 
 class NagiosHelper:
     """
@@ -71,6 +71,8 @@
         return code
 
     def append_message(self, code, msg):
+        # Just to be sure that the | char is not included
+        msg = msg.replace('|', ' ')
         if code > 2 or code < 0:
             self.unknown_message += msg
         if code == 1:
@@ -108,8 +110,7 @@
 
     def getSubArrayElement(self, key, data):
         subElemKey = key[:key.find(self.arrayOpener)]
-        index = int(key[key.find(self.arrayOpener) +
-                        1:key.find(self.arrayCloser)])
+        index = int(key[key.find(self.arrayOpener) + 
1:key.find(self.arrayCloser)])
         remainingKey = key[key.find(self.arrayCloser + self.separator) + 2:]
 
         if key.find(self.arrayCloser + self.separator) == -1:
@@ -128,8 +129,7 @@
                 return (None, 'not_found')
 
     def equals(self, key, value):
-        return self.exists(key) and \
-            str(self.get(key)) in value.split(self.value_separator)
+        return self.exists(key) and str(self.get(key)) in 
value.split(self.value_separator)
 
     def lte(self, key, value):
         return self.exists(key) and float(self.get(key)) <= float(value)
@@ -151,30 +151,34 @@
         Can navigate nested json keys with a dot format
         (Element.Key.NestedKey). Returns (None, 'not_found') if not found
         """
-
         if temp_data != '':
             data = temp_data
         else:
             data = self.data
+
         if len(key) <= 0:
             return data
-        if key.find(self.separator) != -1 and \
-           key.find(self.arrayOpener) != -1:
+
+        if key.find(self.separator) != -1 and key.find(self.arrayOpener) != -1:
             if key.find(self.separator) < key.find(self.arrayOpener):
                 return self.getSubElement(key, data)
             else:
                 return self.getSubArrayElement(key, data)
-        else:
-            if key.find(self.separator) != -1:
-                return self.getSubElement(key, data)
-            else:
-                if key.find(self.arrayOpener) != -1:
-                    return self.getSubArrayElement(key, data)
-                else:
-                    if isinstance(data, dict) and key in data:
-                        return data[key]
-                    else:
-                        return (None, 'not_found')
+
+        if key.find(self.separator) != -1:
+            return self.getSubElement(key, data)
+
+        if key.find(self.arrayOpener) != -1:
+            # If we got an arrayOpener but it's not ([0-9*]) then it might 
just be a string
+            if key[key.find(self.arrayOpener)+1].isnumeric() or 
key[key.find(self.arrayOpener)+1] == "*":
+                return self.getSubArrayElement(key, data)
+            elif key in data:
+                return data[key]
+
+        if isinstance(data, dict) and key in data:
+            return data[key]
+
+        return (None, 'not_found')
 
     def expandKey(self, key, keys):
         if '(*)' not in key:
@@ -229,23 +233,17 @@
         debugPrint(rules_args.debug, "separator: %s" % separator)
         debugPrint(rules_args.debug, "value_separator: %s" % value_separator)
         self.metric_list = self.expandKeys(self.rules.metric_list)
-        self.key_threshold_warning = self.expandKeys(
-            self.rules.key_threshold_warning)
-        self.key_threshold_critical = self.expandKeys(
-            self.rules.key_threshold_critical)
+        self.key_threshold_warning = 
self.expandKeys(self.rules.key_threshold_warning)
+        self.key_threshold_critical = 
self.expandKeys(self.rules.key_threshold_critical)
         self.key_value_list = self.expandKeys(self.rules.key_value_list)
-        self.key_value_list_not = self.expandKeys(
-            self.rules.key_value_list_not)
+        self.key_value_list_not = 
self.expandKeys(self.rules.key_value_list_not)
         self.key_time_list = self.expandKeys(self.rules.key_time_list)
         self.key_list = self.expandKeys(self.rules.key_list)
-        self.key_value_list_critical = self.expandKeys(
-            self.rules.key_value_list_critical)
-        self.key_value_list_not_critical = self.expandKeys(
-            self.rules.key_value_list_not_critical)
+        self.key_value_list_critical = 
self.expandKeys(self.rules.key_value_list_critical)
+        self.key_value_list_not_critical = 
self.expandKeys(self.rules.key_value_list_not_critical)
         self.key_time_list_critical = 
self.expandKeys(self.rules.key_time_list_critical)
         self.key_list_critical = self.expandKeys(self.rules.key_list_critical)
-        self.key_value_list_unknown = self.expandKeys(
-            self.rules.key_value_list_unknown)
+        self.key_value_list_unknown = 
self.expandKeys(self.rules.key_value_list_unknown)
 
     def expandKeys(self, src):
         if src is None:
@@ -271,8 +269,7 @@
             k, v = kv.split(',')
             key, alias = _getKeyAlias(k)
             if not self.helper.equals(key, v):
-                failure += " Key %s mismatch. %s != %s" % (alias, v,
-                                                           
self.helper.get(key))
+                failure += " Key %s mismatch. %s != %s" % (alias, v, 
self.helper.get(key))
         return failure
 
     def checkNonEquality(self, equality_list):
@@ -281,8 +278,7 @@
             k, v = kv.split(',')
             key, alias = _getKeyAlias(k)
             if self.helper.equals(key, v):
-                failure += " Key %s match found. %s == %s" % (alias, v,
-                                                              
self.helper.get(key))
+                failure += " Key %s match found. %s == %s" % (alias, v, 
self.helper.get(key))
         return failure
 
     def checkThreshold(self, key, alias, r):
@@ -302,27 +298,19 @@
                 end = vals[1]
         if(start == '~'):
             if (invert and self.helper.lte(key, end)):
-                failure += " Value (%s) for key %s was less than or equal to 
%s." % \
-                           (self.helper.get(key), alias, end)
+                failure += " Value (%s) for key %s was less than or equal to 
%s." % (self.helper.get(key), alias, end)
             elif (not invert and self.helper.gt(key, end)):
-                failure += " Value (%s) for key %s was greater than %s." % \
-                           (self.helper.get(key), alias, end)
+                failure += " Value (%s) for key %s was greater than %s." % 
(self.helper.get(key), alias, end)
         elif(end == 'infinity'):
             if (invert and self.helper.gte(key, start)):
-                failure += " Value (%s) for key %s was greater than or equal 
to %s." % \
-                           (self.helper.get(key), alias, start)
+                failure += " Value (%s) for key %s was greater than or equal 
to %s." % (self.helper.get(key), alias, start)
             elif (not invert and self.helper.lt(key, start)):
-                failure += " Value (%s) for key %s was less than %s." % \
-                           (self.helper.get(key), alias, start)
+                failure += " Value (%s) for key %s was less than %s." % 
(self.helper.get(key), alias, start)
         else:
-            if (invert and self.helper.gte(key, start) and
-                    self.helper.lte(key, end)):
-                failure += " Value (%s) for key %s was inside the range 
%s:%s." % \
-                           (self.helper.get(key), alias, start, end)
-            elif (not invert and (self.helper.lt(key, start) or
-                                  self.helper.gt(key, end))):
-                failure += " Value (%s) for key %s was outside the range 
%s:%s." % \
-                           (self.helper.get(key), alias, start, end)
+            if (invert and self.helper.gte(key, start) and 
self.helper.lte(key, end)):
+                failure += " Value (%s) for key %s was inside the range 
%s:%s." % (self.helper.get(key), alias, start, end)
+            elif (not invert and (self.helper.lt(key, start) or 
self.helper.gt(key, end))):
+                failure += " Value (%s) for key %s was outside the range 
%s:%s." % (self.helper.get(key), alias, start, end)
 
         return failure
 
@@ -348,25 +336,23 @@
         unit = r[-1]
 
         if unit == 's':
-            tiemduration = timedelta(seconds=duration)
+            time_duration = timedelta(seconds=duration)
         elif unit == 'm':
-            tiemduration = timedelta(minutes=duration)
+            time_duration = timedelta(minutes=duration)
         elif unit == 'h':
-            tiemduration = timedelta(hours=duration)
+            time_duration = timedelta(hours=duration)
         elif unit == 'd':
-            tiemduration = timedelta(days=duration)
+            time_duration = timedelta(days=duration)
         else:
             return " Value (%s) is not a vaild timeduration." % (r)
 
         if not self.helper.exists(key):
-            return " Key (%s) for key %s not Exists." % \
-                           (key, alias)
+            return " Key (%s) for key %s not Exists." % (key, alias)
 
         try:
             timestamp = datetime.fromisoformat(self.helper.get(key))
         except ValueError as ve:
-            return " Value (%s) for key %s is not a Date in ISO format. %s" % \
-                           (self.helper.get(key), alias, ve)
+            return " Value (%s) for key %s is not a Date in ISO format. %s" % 
(self.helper.get(key), alias, ve)
 
         now = datetime.now(timezone.utc)
 
@@ -376,19 +362,15 @@
         age = now - timestamp
 
         if not negative:
-            if age > tiemduration and not invert:
-                failure += " Value (%s) for key %s is older than now-%s%s." % \
-                           (self.helper.get(key), alias, duration, unit)
-            if not age > tiemduration and invert:
-                failure += " Value (%s) for key %s is newer than now-%s%s." % \
-                           (self.helper.get(key), alias, duration, unit)
+            if age > time_duration and not invert:
+                failure += " Value (%s) for key %s is older than now-%s%s." % 
(self.helper.get(key), alias, duration, unit)
+            if not age > time_duration and invert:
+                failure += " Value (%s) for key %s is newer than now-%s%s." % 
(self.helper.get(key), alias, duration, unit)
         else:
-            if age < -tiemduration and not invert:
-                failure += " Value (%s) for key %s is newer than now+%s%s." % \
-                           (self.helper.get(key), alias, duration, unit)
-            if not age < -tiemduration and invert:
-                failure += " Value (%s) for key %s is older than now+%s%s.." % 
\
-                           (self.helper.get(key), alias, duration, unit)
+            if age < -time_duration and not invert:
+                failure += " Value (%s) for key %s is newer than now+%s%s." % 
(self.helper.get(key), alias, duration, unit)
+            if not age < -time_duration and invert:
+                failure += " Value (%s) for key %s is older than now+%s%s.." % 
(self.helper.get(key), alias, duration, unit)
 
         return failure
 
@@ -441,10 +423,12 @@
         Return a Nagios specific performance metrics string given keys
         and parameter definitions
         """
-
         metrics = ''
         warning = ''
         critical = ''
+
+        kv = dict(self.rules.metric_value_mapping) if hasattr(self.rules, 
'metric_value_mapping') else {}
+
         if self.metric_list is not None:
             for metric in self.metric_list:
                 key = metric
@@ -457,11 +441,13 @@
                     if len(vals) == 4:
                         key, uom, warn_range, crit_range = vals
                     if len(vals) == 6:
-                        key, uom, warn_range, crit_range, \
-                            minimum, maximum = vals
+                        key, uom, warn_range, crit_range, minimum, maximum = 
vals
                 key, alias = _getKeyAlias(key)
                 if self.helper.exists(key):
-                    metrics += "'%s'=%s" % (alias, self.helper.get(key))
+                    value = self.helper.get(key)
+                    # Apply the value mapping if it exists
+                    v = kv.get(str(value), value)
+                    metrics += "'%s'=%s" % (alias, v)
                     if uom:
                         metrics += uom
                     if warn_range is not None:
@@ -471,17 +457,14 @@
                         critical += self.checkThreshold(key, alias, crit_range)
                         metrics += ";%s" % crit_range
                     if minimum is not None:
-                        critical += self.checkThreshold(key, alias, minimum +
-                                                        ':')
+                        critical += self.checkThreshold(key, alias, minimum + 
':')
                         metrics += ";%s" % minimum
                     if maximum is not None:
-                        critical += self.checkThreshold(key, alias, '~:' +
-                                                        maximum)
+                        critical += self.checkThreshold(key, alias, '~:' + 
maximum)
                         metrics += ";%s" % maximum
                 metrics += ' '
         return ("%s" % metrics, warning, critical)
 
-
 def parseArgs(args):
     """
     CLI argument definitions and parsing
@@ -493,81 +476,52 @@
         formatter_class=argparse.RawDescriptionHelpFormatter
     )
 
-    parser.add_argument('-d', '--debug', action='store_true',
-                        help='debug mode')
-    parser.add_argument('-v', '--verbose', action='count', default=0,
-                        help='Verbose mode. Multiple -v options increase the 
verbosity')
-
-    parser.add_argument('-s', '--ssl', action='store_true',
-                        help='use TLS to connect to remote host')
-    parser.add_argument('-H', '--host', dest='host',
-                        required=not ('-V' in args or '--version' in args),
-                        help='remote host to query')
-    parser.add_argument('-k', '--insecure', action='store_true',
-                        help='do not check server SSL certificate')
+    parser.add_argument('-d', '--debug', action='store_true', help='Debug 
mode')
+    parser.add_argument('-v', '--verbose', action='count', default=0, 
help='Verbose mode. Multiple -v options increase the verbosity')
+    parser.add_argument('-s', '--ssl', action='store_true', help='Use TLS to 
connect to remote host')
+    parser.add_argument('-H', '--host', dest='host', required=not ('-V' in 
args or '--version' in args), help='Remote host to query')
+    parser.add_argument('-k', '--insecure', action='store_true', help='Do not 
check server SSL certificate')
     parser.add_argument('-X', '--request', dest='method', default='GET', 
choices=['GET', 'POST'],
                         help='Specifies a custom request method to use when 
communicating  with  the HTTP server')
-    parser.add_argument('-V', '--version', action='store_true',
-                        help='print version of this plugin')
-    parser.add_argument('--cacert',
-                        dest='cacert', help='SSL CA certificate')
-    parser.add_argument('--cert',
-                        dest='cert', help='SSL client certificate')
-    parser.add_argument('--key', dest='key',
-                        help='SSL client key ( if not bundled into the cert )')
+    parser.add_argument('-V', '--version', action='store_true', help='Print 
version of this plugin')
+    parser.add_argument('--cacert', dest='cacert', help='SSL CA certificate')
+    parser.add_argument('--cert', dest='cert', help='SSL client certificate')
+    parser.add_argument('--key', dest='key', help='SSL client key ( if not 
bundled into the cert )')
     parser.add_argument('-P', '--port', dest='port', help='TCP port')
     parser.add_argument('-p', '--path', dest='path', help='Path')
-    parser.add_argument('-t', '--timeout', type=int,
-                        help='Connection timeout (seconds)')
+    parser.add_argument('-t', '--timeout', type=int, help='Connection timeout 
(seconds)', default=10)
     parser.add_argument('--unreachable-state', type=int, default=3,
                         help='Exit with specified code when the URL is 
unreachable. Examples: 1 for Warning, 2 for Critical, 3 for Unknown (default: 
3)')
     parser.add_argument('--invalid-json-state', type=int, default=3,
                         help='Exit with specified code when no valid JSON is 
returned. Examples: 1 for Warning, 2 for Critical, 3 for Unknown (default: 3)')
-    parser.add_argument('-B', '--basic-auth', dest='auth',
-                        help='Basic auth string "username:password"')
-    parser.add_argument('-D', '--data', dest='data',
-                        help='The http payload to send as a POST')
-    parser.add_argument('-A', '--headers', dest='headers',
-                        help='The http headers in JSON format.')
+    parser.add_argument('-B', '--basic-auth', dest='auth', help='Basic auth 
string "username:password"')
+    parser.add_argument('-D', '--data', dest='data', help='The HTTP payload to 
send as a POST')
+    parser.add_argument('-A', '--headers', dest='headers', help='The HTTP 
headers in JSON format.')
     parser.add_argument('-f', '--field_separator', dest='separator',
-                        help='''JSON Field separator, defaults to ".";
-                        Select element in an array with "(" ")"''')
-    parser.add_argument('-F', '--value_separator', dest='value_separator',
-                        help='''JSON Value separator, defaults to ":"''')
-    parser.add_argument('-w', '--warning', dest='key_threshold_warning',
-                        nargs='*',
-                        help='''Warning threshold for these values
+                        help='''JSON Field separator, defaults to "."; Select 
element in an array with "(" ")"''')
+    parser.add_argument('-F', '--value_separator', dest='value_separator', 
help='''JSON Value separator, defaults to ":"''')
+    parser.add_argument('-w', '--warning', dest='key_threshold_warning', 
nargs='*', help='''Warning threshold for these values
                         (key1[>alias],WarnRange key2[>alias],WarnRange).
                         WarnRange is in the format [@]start:end, more
                         information at
                         nagios-plugins.org/doc/guidelines.html.''')
-    parser.add_argument('-c', '--critical', dest='key_threshold_critical',
-                        nargs='*',
+    parser.add_argument('-c', '--critical', dest='key_threshold_critical', 
nargs='*',
                         help='''Critical threshold for these values
                         (key1[>alias],CriticalRange key2[>alias],CriticalRange.
                         CriticalRange is in the format [@]start:end, more
                         information at
                         nagios-plugins.org/doc/guidelines.html.''')
-    parser.add_argument('-e', '--key_exists', dest='key_list', nargs='*',
-                        help='''Checks existence of these keys to determine
+    parser.add_argument('-e', '--key_exists', dest='key_list', nargs='*', 
help='''Checks existence of these keys to determine
                         status. Return warning if key is not present.''')
-    parser.add_argument('-E', '--key_exists_critical', 
dest='key_list_critical',
-                        nargs='*',
-                        help='''Same as -e but return critical if key is
-                        not present.''')
-    parser.add_argument('-q', '--key_equals', dest='key_value_list',
-                        action='extend',
-                        nargs='*',
+    parser.add_argument('-E', '--key_exists_critical', 
dest='key_list_critical', nargs='*', help='Same as -e but return critical if 
key is not present.')
+    parser.add_argument('-q', '--key_equals', dest='key_value_list', 
action='extend', nargs='*',
                         help='''Checks equality of these keys and values
                         (key[>alias],value key2,value2) to determine status.
                         Multiple key values can be delimited with colon
                         (key,value1:value2). Return warning if equality
                         check fails''')
-    parser.add_argument('-Q', '--key_equals_critical', 
dest='key_value_list_critical',
-                        action='extend',
-                        nargs='*',
-                        help='''Same as -q but return critical if
-                        equality check fails.''')
+    parser.add_argument('-Q', '--key_equals_critical', 
dest='key_value_list_critical', action='extend', nargs='*',
+                        help='Same as -q but return critical if equality check 
fails.')
     parser.add_argument('--key_time', dest='key_time_list', nargs='*',
                         help='''Checks a Timestamp of these keys and values
                         (key[>alias],value key2,value2) to determine status.
@@ -577,41 +531,45 @@
                         With at it return warning if the key is jounger
                         than the value (ex.: @30s,@10m,@2h,@3d,...).
                         With Minus you can shift the time in the future.''')
-    parser.add_argument('--key_time_critical',
-                        dest='key_time_list_critical', nargs='*',
-                        help='''Same as --key_time but return critical if
-                        Timestamp age fails.''')
-    parser.add_argument('-u', '--key_equals_unknown',
-                        dest='key_value_list_unknown', nargs='*',
-                        help='''Same as -q but return unknown if
-                        equality check fails.''')
-    parser.add_argument('-y', '--key_not_equals',
-                        dest='key_value_list_not', nargs='*',
+    parser.add_argument('--key_time_critical', dest='key_time_list_critical', 
nargs='*',
+                        help='Same as --key_time but return critical if 
timestamp age fails.')
+    parser.add_argument('-u', '--key_equals_unknown', 
dest='key_value_list_unknown', nargs='*',
+                        help='Same as -q but return unknown if equality check 
fails.')
+    parser.add_argument('-y', '--key_not_equals', dest='key_value_list_not', 
nargs='*',
                         help='''Checks equality of these keys and values
                         (key[>alias],value key2,value2) to determine status.
                         Multiple key values can be delimited with colon
                         (key,value1:value2). Return warning if equality
                         check succeeds''')
-    parser.add_argument('-Y', '--key_not_equals_critical',
-                        dest='key_value_list_not_critical', nargs='*',
-                        help='''Same as -q but return critical if equality
-                        check succeeds.''')
-    parser.add_argument('-m', '--key_metric', dest='metric_list',
-                        action='extend',
-                        nargs='*',
+    parser.add_argument('-Y', '--key_not_equals_critical', 
dest='key_value_list_not_critical', nargs='*',
+                        help='Same as -q but return critical if equality check 
succeeds.')
+    parser.add_argument('-m', '--key_metric', dest='metric_list', 
action='extend', nargs='*',
                         help='''Gathers the values of these keys (key[>alias],
                         UnitOfMeasure,WarnRange,CriticalRange,Min,Max) for
                         Nagios performance data. More information about Range
-                        format and units of measure for nagios can be found at
+                        format and units of measure for Nagios can be found at
                         nagios-plugins.org/doc/guidelines.html
                         Additional formats for this parameter are:
                         (key[>alias]), (key[>alias],UnitOfMeasure),
                         (key[>alias],UnitOfMeasure,WarnRange,
                         CriticalRange).''')
+    parser.add_argument('-M', dest='metric_value_mapping', 
metavar="KEY=VALUE", type=key_value_pair, action="append", default=[],
+                        help='''Map the values of the gathered metric to the 
given values.
+                        This can be used to map non-numeric values to numeric 
values, e.g. -M Up=1. Can used multiple times.
+                        This flag is meant to be used with the -m flag.''')
 
     return parser.parse_args(args)
 
 
+def key_value_pair(value):
+    """
+    Small helper to split key=value arguments into a tuple.
+    """
+    parts = value.split('=', 1)
+    if len(parts) != 2:
+        raise argparse.ArgumentTypeError('invalid format %s. Expected 
key=value' % (value))
+    return (parts[0], parts[1])
+
 def debugPrint(debug_flag, message):
     """
     Print debug messages if -d is set.
@@ -683,19 +641,19 @@
         debugPrint(args.debug, "Headers:\n %s" % headers)
         for header in headers:
             req.add_header(header, headers[header])
-    if args.timeout and args.data:
-        databytes = str(args.data).encode()
-        response = urllib.request.urlopen(req, timeout=args.timeout,
-                                          data=databytes, context=context)
-    elif args.timeout:
-        response = urllib.request.urlopen(req, timeout=args.timeout,
-                                          context=context)
-    elif args.data:
-        databytes = str(args.data).encode()
-        response = urllib.request.urlopen(req, data=databytes, context=context)
-    else:
-        # pylint: disable=consider-using-with
-        response = urllib.request.urlopen(req, context=context)
+
+    try:
+        if args.data:
+            databytes = str(args.data).encode()
+            response = urllib.request.urlopen(req, data=databytes, 
timeout=args.timeout, context=context)
+        else:
+            # pylint: disable=consider-using-with
+            response = urllib.request.urlopen(req, timeout=args.timeout, 
context=context)
+    except TimeoutError:
+        nagios = NagiosHelper()
+        nagios.append_message(args.unreachable_state, "  %s socket timeout 
after %s seconds" % (url, args.timeout))
+        print(nagios.getMessage())
+        sys.exit(nagios.getCode())
 
     return response.read()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/requirements-dev.txt 
new/nagios-http-json-2.4.0/requirements-dev.txt
--- old/nagios-http-json-2.3.1/requirements-dev.txt     2026-02-25 
08:29:54.000000000 +0100
+++ new/nagios-http-json-2.4.0/requirements-dev.txt     2026-05-04 
08:54:24.000000000 +0200
@@ -1,2 +1,2 @@
-coverage==7.8.0
-pylint==3.3.6
+coverage==7.13.5
+pylint==4.0.5
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/test/test_args.py 
new/nagios-http-json-2.4.0/test/test_args.py
--- old/nagios-http-json-2.3.1/test/test_args.py        2026-02-25 
08:29:54.000000000 +0100
+++ new/nagios-http-json-2.4.0/test/test_args.py        2026-05-04 
08:54:24.000000000 +0200
@@ -32,3 +32,7 @@
         parser = parseArgs(['-H', 'foobar', '-f', '_', '-F', '_'])
         self.assertEqual(parser.separator, '_')
         self.assertEqual(parser.value_separator, '_')
+
+    def test_parser_with_value_mapping(self):
+        parser = parseArgs(['-H', 'foobar', '-M', 'key=value'])
+        self.assertEqual(parser.metric_value_mapping, [('key', 'value')])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/test/test_check_http_json.py 
new/nagios-http-json-2.4.0/test/test_check_http_json.py
--- old/nagios-http-json-2.3.1/test/test_check_http_json.py     2026-02-25 
08:29:54.000000000 +0100
+++ new/nagios-http-json-2.4.0/test/test_check_http_json.py     2026-05-04 
08:54:24.000000000 +0200
@@ -234,6 +234,40 @@
         self.check_data(RulesHelper().dash_c(['(*).value,@1:5']),
                         '[{"value": 5},{"value": 1000}]', CRITICAL_CODE)
 
+    def test_metrics_value_mapping(self):
+        data = json.loads('{"status": "Up"}')
+        expected = ("'status'=0 ", '', '')
+
+        processor = JsonRuleProcessor(data, parseArgs(['-H', 'foobar', '-m', 
'status', '-M', 'Up=0']))
+        actual = processor.checkMetrics()
+
+        self.assertEqual(actual, expected)
+
+        data = json.loads('{"status": "Down"}')
+        expected = ("'status'=1 ", '', '')
+
+        processor = JsonRuleProcessor(data, parseArgs(['-H', 'foobar', '-m', 
'status', '-M', 'Up=0', '-M', 'Down=1']))
+        actual = processor.checkMetrics()
+
+        self.assertEqual(actual, expected)
+
+    def test_metrics_value_mapping_datatypes(self):
+        data = json.loads('{"status": "123"}')
+        expected = ("'status'=foo ", '', '')
+        # Test with string value as target
+        processor = JsonRuleProcessor(data, parseArgs(['-H', 'foobar', '-m', 
'status', '-M', '123=foo']))
+        actual = processor.checkMetrics()
+
+        self.assertEqual(actual, expected)
+
+        data = json.loads('{"status": 123}')
+        expected = ("'status'=foo ", '', '')
+        # Test with string value as source and target
+        processor = JsonRuleProcessor(data, parseArgs(['-H', 'foobar', '-m', 
'status', '-M', '123=foo']))
+        actual = processor.checkMetrics()
+
+        self.assertEqual(actual, expected)
+
     def test_separator(self):
         rules = RulesHelper()
         rules.separator = '_'
@@ -319,7 +353,7 @@
             data = "{\"timestamp\": \"%s\",\"timestamp2\": \"%s\"}" % (now, 
now)
 
             self.check_data(RulesHelper().dash_dash_key_time(['timestamp,30s', 
'timestamp2,30s']), data, OK_CODE)
-            
+
             
self.check_data(RulesHelper().dash_dash_key_time(['timestamp,30m']), data, 
OK_CODE)
             
self.check_data(RulesHelper().dash_dash_key_time_critical(['timestamp,1h']), 
data, OK_CODE)
             
self.check_data(RulesHelper().dash_dash_key_time(['timestamp,3h']), data, 
OK_CODE)
@@ -448,3 +482,26 @@
             
self.check_data(RulesHelper().dash_dash_key_time_critical(['timestamp,@-1h']), 
data, CRITICAL_CODE)
             
self.check_data(RulesHelper().dash_dash_key_time(['timestamp,@-3h']), data, 
WARNING_CODE)
             
self.check_data(RulesHelper().dash_dash_key_time_critical(['timestamp,@-2d']), 
data, CRITICAL_CODE)
+
+    def test_bracket_in_key(self):
+        """
+        https://github.com/drewkerrigan/nagios-http-json/issues/76
+        """
+
+        rules = RulesHelper()
+
+        # This should work
+        data = '[{"update status": "failure"}]'
+        self.check_data(rules.dash_q(['(*).update status,failure']), data, 
OK_CODE)
+
+        data = '[{"update (status)": "failure"}]'
+        self.check_data(rules.dash_q(['(*).update (status),failure']), data, 
OK_CODE)
+
+        data = '[{"update (((status)": "failure"}]'
+        self.check_data(rules.dash_q(['(*).update (((status),failure']), data, 
OK_CODE)
+
+        data = '[{"update )status)": "failure"}]'
+        self.check_data(rules.dash_q(['(*).update )status),failure']), data, 
OK_CODE)
+
+        data = '[{"update (status": "failure"}]'
+        self.check_data(rules.dash_q(['(*).update (status),failure']), data, 
WARNING_CODE)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/test/test_main.py 
new/nagios-http-json-2.4.0/test/test_main.py
--- old/nagios-http-json-2.3.1/test/test_main.py        2026-02-25 
08:29:54.000000000 +0100
+++ new/nagios-http-json-2.4.0/test/test_main.py        2026-05-04 
08:54:24.000000000 +0200
@@ -129,3 +129,16 @@
 
         self.assertTrue('Error loading SSL CA' in str(mock_print.call_args))
         self.assertEqual(test.exception.code, 3)
+
+    @mock.patch('builtins.print')
+    @mock.patch('urllib.request.urlopen')
+    def test_main_with_timeout(self, mock_request, mock_print):
+        args = ['-H', 'localhost']
+
+        mock_request.side_effect = TimeoutError('timeout')
+
+        with self.assertRaises(SystemExit) as test:
+            main(args)
+
+        self.assertTrue('timeout' in str(mock_print.call_args))
+        self.assertEqual(test.exception.code, 3)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/nagios-http-json-2.3.1/test/test_nagioshelper.py 
new/nagios-http-json-2.4.0/test/test_nagioshelper.py
--- old/nagios-http-json-2.3.1/test/test_nagioshelper.py        2026-02-25 
08:29:54.000000000 +0100
+++ new/nagios-http-json-2.4.0/test/test_nagioshelper.py        2026-05-04 
08:54:24.000000000 +0200
@@ -49,3 +49,9 @@
         helper = NagiosHelper()
         helper.performance_data = 'foobar'
         self.assertEqual('OK: foobar Status OK. |foobar', helper.getMessage())
+
+    def test_getmessage_with_perfdata_separator(self):
+
+        helper = NagiosHelper()
+        helper.append_message(1, 'exa|mple')
+        self.assertEqual('WARNING: Status WARNING. exa mple', 
helper.getMessage())

Reply via email to