Repository: kibitzr/kibitzr (language: python)

Issue:
Add http notifier
Please and a notifier which executes http requests. This could be used to communicate with web hooks or other web based API. For example sending messages via [Gotify](https://gotify.net/).


Discussion / hints:
There are so many nuances to send HTTP requests, that I've left this task to `shell` and `python` notifiers.
Consider authentication, methods, payloads, content types. Exposing all these options through YAML config seems to be not maintainable.
But you're welcome to add a PR with `gotify` notifier, that will load credentials from `kibitzr-creds` and POST the request according to protocol.
Yes, you're right.

I tried `shell` first, but the Docker container does not contain `curl`. So I'm using `python`:

```yaml
notify:
  - python: |
      import requests
      requests.post('http://localhost:8008/message?token=<apptoken>', json={
          "message": content,
          "title": "This is my title"
      })
```

---

I have already taken a look at the code. A gotify notifier should not be very complicated. I will try to create a first draft in the next few days. :)

Fix patch:
diff --git a/docs/gotify.rst b/docs/gotify.rst
new file mode 100644
index 0000000..9a1ec8d
--- /dev/null
+++ b/docs/gotify.rst
@@ -0,0 +1,57 @@
+.. _gotify:
+
+=================
+Gotify Notifier
+=================
+
+Kibitzr supports sending notifications via Gotify_.
+
+Configuration
+-------------
+
+The Gotify notifier needs an ``url`` and an application ``token`` to send notifications.
+
+Example of ``kibitzr-creds.yml`` file:
+
+.. code-block:: yaml
+
+    gotify:
+        url: https://gotify.example.net/
+        token: A0dIInnCs1J1zNN
+
+
+Now the notifier can be configured in ``kibitzr.yml`` using following syntax:
+
+.. code-block:: yaml
+
+    checks:
+      ...
+        notify:
+          - gotify
+
+
+Some defaults can be overridden for each check:
+
+.. code-block:: yaml
+
+    checks:
+      ...
+        notify:
+          - gotify:
+                title: Important Notification # default: kibitzr
+                priority: 7 # default: 4
+
+
+The request to the Gotify server performs an SSL certificate verification.
+If you are **aware of the implications** and still want to bypass this verification,
+you can do that by adding a ``verify: false`` flag to the configuration:
+
+.. code-block:: yaml
+
+    gotify:
+        url: https://gotify.example.net/
+        token: A0dIInnCs1J1zNN
+        verify: false
+
+
+.. _Gotify: https://gotify.net/
diff --git a/docs/notifiers.rst b/docs/notifiers.rst
index 7f74743..3c93871 100644
--- a/docs/notifiers.rst
+++ b/docs/notifiers.rst
@@ -14,10 +14,11 @@ Kibitzr supports following notifier types:
 3. ``slack`` - Trigger `Slack Incoming Webhook`_
 4. ``telegram`` - Send message through :ref:`private Telegram Bot <telegram>`
 5. ``zapier`` - Trigger `Zapier Catch Hook`_
-6. ``gitter`` - Or post to gitter's chat
-7. ``python`` - Run :ref:`Python script <python>`
-8. ``shell`` - Run :ref:`shell script <shell>`
-9. ``stash`` - Save to persistent global key-value storage; See :ref:`stash` for details
+6. ``gitter`` - Post to gitter's chat
+7. ``gotify`` - Push notification via :ref:`Gotify <gotify>`
+8. ``python`` - Run :ref:`Python script <python>`
+9. ``shell`` - Run :ref:`shell script <shell>`
+10. ``stash`` - Save to persistent global key-value storage; See :ref:`stash` for details
 
 Each notifier requires different configuration.
 For the sake of security, sensitive information
@@ -57,3 +58,7 @@ Example configurations
 
     zapier:
         url: https://hooks.zapier.com/hooks/catch/1670195/9asu13/
+
+    gotify:
+        url: https://gotify.example.de/
+        token: A0dIInnCs1J1zNN
diff --git a/kibitzr/notifier/gotify.py b/kibitzr/notifier/gotify.py
new file mode 100644
index 0000000..32d2650
--- /dev/null
+++ b/kibitzr/notifier/gotify.py
@@ -0,0 +1,53 @@
+import logging
+import urllib3
+
+from ..conf import settings
+
+
+logger = logging.getLogger(__name__)
+
+
+class GotifyNotify(object):
+    def __init__(self, value):
+        import requests
+        self.session = requests.Session()
+        gotify_creds = settings().creds['gotify']
+        self.url = gotify_creds['url'].rstrip('/') + '/message'
+        self.token = gotify_creds['token']
+        self.verify = gotify_creds.get('verify', True)
+        if not self.verify:
+            # Since certificate validation was disabled, do not show the warning
+            urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+        if value is None:
+            value = {}
+        self.title = value.get('title', 'kibitzr')
+        self.priority = value.get('priority', 4)
+
+    def post(self, report):
+        logger.info("Executing Gotify notifier")
+        response = self.session.post(
+            url=self.url,
+            json=self.json(report),
+            params=self.params(),
+            verify=self.verify
+        )
+        logger.debug(response.text)
+        response.raise_for_status()
+        return response
+    __call__ = post
+
+    def params(self):
+        return {
+            'token': self.token
+        }
+
+    def json(self, report):
+        return {
+            'title': self.title,
+            'message': report,
+            'priority': self.priority
+        }
+
+
+def notify_factory(conf, value):
+    return GotifyNotify(value)


Test patch:
diff --git a/tests/unit/notifiers/test_gotify.py b/tests/unit/notifiers/test_gotify.py
new file mode 100644
index 0000000..cfbcc45
--- /dev/null
+++ b/tests/unit/notifiers/test_gotify.py
@@ -0,0 +1,81 @@
+import pytest
+from kibitzr.notifier.gotify import GotifyNotify
+
+from ...compat import mock
+from ...helpers import SettingsMock
+
+
+@pytest.fixture()
+def settings():
+    """Override native settings singleton with empty one"""
+    return SettingsMock.instance()
+
+
+def test_gotify_sample(settings):
+    settings.creds.update({
+        'gotify': {
+            'url': 'http://localhost:8080',
+            'token': '123456',
+        }
+    })
+    value = {}
+    notify = GotifyNotify(value=value)
+    with mock.patch.object(notify.session, 'post') as fake_post:
+        notify('report')
+    fake_post.assert_called_once_with(
+        url='http://localhost:8080/message',
+        json={
+            'title': 'kibitzr',
+            'message': 'report',
+            'priority': 4
+        },
+        params={'token': '123456'},
+        verify=True
+    )
+
+
+def test_gotify_no_verification(settings):
+    settings.creds.update({
+        'gotify': {
+            'url': 'https://localhost:8080',
+            'token': '123456',
+            'verify': False,
+        }
+    })
+    value = {}
+    notify = GotifyNotify(value=value)
+    with mock.patch.object(notify.session, 'post') as fake_post:
+        notify('report')
+    fake_post.assert_called_once_with(
+        url='https://localhost:8080/message',
+        json={
+            'title': 'kibitzr',
+            'message': 'report',
+            'priority': 4,
+        },
+        params={'token': '123456'},
+        verify=False
+    )
+
+
+def test_gotify_override_defaults(settings):
+    settings.creds.update({
+        'gotify': {
+            'url': 'http://localhost:8080',
+            'token': '123456',
+        }
+    })
+    value = {'title': 'Important Notification', 'priority': 7}
+    notify = GotifyNotify(value=value)
+    with mock.patch.object(notify.session, 'post') as fake_post:
+        notify('report')
+    fake_post.assert_called_once_with(
+        url='http://localhost:8080/message',
+        json={
+            'title': 'Important Notification',
+            'message': 'report',
+            'priority': 7
+        },
+        params={'token': '123456'},
+        verify=True
+    )