Compare commits

..

No commits in common. "master" and "v3.2.0" have entirely different histories.

45 changed files with 646 additions and 4025 deletions

28
.github/workflows/docker-hub.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: Publish
on:
push:
branches:
- 'master'
tags:
- 'v*'
jobs:
docker-hub:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: docker/metadata-action@v3
id: meta
with:
images: nim65s/matrix-webhook
- uses: docker/login-action@v1
with:
username: nim65s
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

8
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,8 @@
name: Lints
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: psf/black@stable

19
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,19 @@
name: Release on GitHub & PyPI
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: pip install -U poetry twine wheel
- run: poetry build
- run: twine upload --non-interactive -u __token__ -p ${{ secrets.PYPI_TOKEN }} dist/*
- run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- run: gh release create -t "Release ${{ env.TAG}}" -n "$(awk '/## \[${{ env.TAG }}] - /{flag=1;next}/## \[/{flag=0}flag' CHANGELOG.md)" ${{ env.TAG }} dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

9
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,9 @@
name: Tests
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: docker-compose -f test.yml up --exit-code-from tests
- uses: codecov/codecov-action@v1

2
.gitignore vendored
View file

@ -3,5 +3,3 @@
.mypy_cache .mypy_cache
coverage.xml coverage.xml
htmlcov htmlcov
**__pycache__
config.yaml

View file

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v4.0.1
hooks: hooks:
- id: check-added-large-files - id: check-added-large-files
- id: check-ast - id: check-ast
@ -14,7 +14,7 @@ repos:
- id: mixed-line-ending - id: mixed-line-ending
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/psf/black - repo: https://github.com/psf/black
rev: 22.10.0 rev: 21.7b0
hooks: hooks:
- id: black - id: black
language_version: python3 language_version: python3
@ -25,12 +25,11 @@ repos:
args: args:
- --ignore=D200,D203,D212 - --ignore=D200,D203,D212
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.0.0 rev: 3.9.2
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/asottile/pyupgrade - repo: https://gitlab.com/smop/pre-commit-hooks
rev: v3.2.2 rev: v1.0.0
hooks: hooks:
- id: pyupgrade - id: check-poetry
args: - id: check-gitlab-ci
- --py38-plus

View file

@ -6,47 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [v3.5.0] - 2022-09-07 ## [3.2.0] - 2021-08-27
- Add formatter for grafana 9
in [#45](https://github.com/nim65s/matrix-webhook/pull/45)
by [@svenseeberg](https://github.com/svenseeberg)
## [v3.4.0] - 2022-08-12
- fix tests
- add `matrix-webhook` script
in [#25](https://github.com/nim65s/matrix-webhook/pull/25)
and [#35](https://github.com/nim65s/matrix-webhook/pull/35)
by [@a7p](https://github.com/a7p)
- publish linux/arm64 image
in [#37](https://github.com/nim65s/matrix-webhook/pull/35)
by [@kusold](https://github.com/kusold)
- update badges
- setup dependabot
- misc upgrades from poetry update, pre-commit.ci, and dependabot
## [v3.3.0] - 2022-03-04
- add pyupgrade
- add gitlab formatter for google chat & microsoft teams
in [#21](https://github.com/nim65s/matrix-webhook/pull/21)
by [@GhislainC](https://github.com/GhislainC)
- join room before sending message
in [#12](https://github.com/nim65s/matrix-webhook/pull/12)
by [@bboehmke](https://github.com/bboehmke)
- Changed --api-key and envvar API_KEY to --api-keys and API_KEYS respectively
- Changed handling of api key to use a list instead of single value
can be used
- Changed the formatters to a more plugin-based approach where each formatter is
its own <formattername>.py file in formatters directory
- Added pingdom formatter (currently handling http, dns, and tcp probe types)
## [v3.2.1] - 2021-08-28
- fix changelog
## [v3.2.0] - 2021-08-27
- add github & grafana formatters - add github & grafana formatters
- add formatted_body to bypass markdown with direct - add formatted_body to bypass markdown with direct
@ -58,15 +18,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Publish releases also on github from github actions - Publish releases also on github from github actions
- fix tests for recent synapse docker image - fix tests for recent synapse docker image
## [v3.1.1] - 2021-07-18 ## [3.1.1] - 2021-07-18
## [v3.1.0] - 2021-07-18 ## [3.1.0] - 2021-07-18
- Publish on PyPI & Docker Hub with Github Actions - Publish on PyPI & Docker Hub with Github Actions
in [#10](https://github.com/nim65s/matrix-webhook/pull/10) in [#10](https://github.com/nim65s/matrix-webhook/pull/10)
by [@nim65s](https://github.com/nim65s) by [@nim65s](https://github.com/nim65s)
## [v3.0.0] - 2021-07-18 ## [3.0.0] - 2021-07-18
- Simplify code - Simplify code
in [#1](https://github.com/nim65s/matrix-webhook/pull/1) in [#1](https://github.com/nim65s/matrix-webhook/pull/1)
@ -84,20 +44,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
in [#9](https://github.com/nim65s/matrix-webhook/pull/9) in [#9](https://github.com/nim65s/matrix-webhook/pull/9)
by [@nim65s](https://github.com/nim65s) by [@nim65s](https://github.com/nim65s)
## [v2.0.0] - 2020-03-14 ## [2.0.0] - 2020-03-14
- Update to matrix-nio & aiohttp & markdown - Update to matrix-nio & aiohttp & markdown
## [v1.0.0] - 2020-02-14 ## [1.0.0] - 2020-02-14
- First release with matrix-client & http.server - First release with matrix-client & http.server
[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.5.0...master [Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...master
[v3.5.0]: https://github.com/nim65s/matrix-webhook/compare/v3.4.0...v3.5.0 [3.2.0] https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0
[v3.4.0]: https://github.com/nim65s/matrix-webhook/compare/v3.3.0...v3.4.0 [3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1
[v3.3.0]: https://github.com/nim65s/matrix-webhook/compare/v3.2.1...v3.3.0 [3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0
[v3.2.1]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...v3.2.1 [3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0
[v3.2.0]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0 [2.0.0]: https://github.com/nim65s/matrix-webhook/compare/v1.0.0...v2.0.0
[v3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1 [1.0.0]: https://github.com/nim65s/matrix-webhook/releases/tag/v1.0.0
[v3.1.0]: https://github.com/nim65s/matrix-webhook/compare/v3.0.0...v3.1.0
[v3.0.0]: https://github.com/nim65s/matrix-webhook/compare/v2.0.0...v3.0.0
[v2.0.0]: https://github.com/nim65s/matrix-webhook/compare/v1.0.0...v2.0.0
[v1.0.0]: https://github.com/nim65s/matrix-webhook/releases/tag/v1.0.0

View file

@ -1,55 +1,51 @@
# Matrix Webhook # Matrix Webhook
[![Tests](https://github.com/nim65s/matrix-webhook/actions/workflows/test.yml/badge.svg)](https://github.com/nim65s/matrix-webhook/actions/workflows/test.yml)
[![Lints](https://github.com/nim65s/matrix-webhook/actions/workflows/lint.yml/badge.svg)](https://github.com/nim65s/matrix-webhook/actions/workflows/lint.yml)
[![Docker-Hub](https://github.com/nim65s/matrix-webhook/actions/workflows/docker-hub.yml/badge.svg)](https://hub.docker.com/r/nim65s/matrix-webhook)
[![Release](https://github.com/nim65s/matrix-webhook/actions/workflows/release.yml/badge.svg)](https://pypi.org/project/matrix-webhook/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![codecov](https://codecov.io/gh/nim65s/matrix-webhook/branch/master/graph/badge.svg?token=BLGISGCYKG)](https://codecov.io/gh/nim65s/matrix-webhook)
Post a message to a matrix room with a simple HTTP POST Post a message to a matrix room with a simple HTTP POST
This is my own fork of https://github.com/nim65s/matrix-webhook
It adds a yaml configuration with multi-api key endpoints and moves the filtes
to more of a plugin-based system
## Install ## Install
For now, clone this repo and run `pip install .` ```
python3 -m pip install matrix-webhook
# OR
docker pull nim65s/matrix-webhook
```
## Start ## Start
Create a matrix user for the bot, and launch this app with the following arguments and/or environment variables Create a matrix user for the bot, make it join the rooms you want it to talk into, and launch it with the following
(environment variables update defaults, arguments take precedence): arguments or environment variables:
``` ```
matrix-webhook -h
# OR
python -m matrix_webhook -h python -m matrix_webhook -h
# OR # OR
docker run --rm -it nim65s/matrix-webhook -h docker run --rm -it nim65s/matrix-webhook -h
``` ```
``` ```
usage: python -m matrix_webhook [-h] [-H HOST] [-P PORT] [-u MATRIX_URL] usage: python -m matrix_webhook [-h] [-H HOST] [-P PORT] [-u MATRIX_URL] -i MATRIX_ID -p MATRIX_PW -k API_KEY [-v]
[-i MATRIX_ID] [-p MATRIX_PW] [-k API_KEYS]
[-c CONFIG] [-v]
Configuration for Matrix Webhook. Configuration for Matrix Webhook.
options:
optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-H HOST, --host HOST host to listen to. Default: `''`. Environment -H HOST, --host HOST host to listen to. Default: `''`. Environment variable: `HOST`
variable: `HOST` -P PORT, --port PORT port to listed to. Default: 4785. Environment variable: `PORT`
-P PORT, --port PORT port to listed to. Default: 4785. Environment
variable: `PORT`
-u MATRIX_URL, --matrix-url MATRIX_URL -u MATRIX_URL, --matrix-url MATRIX_URL
matrix homeserver url. Default: `https://matrix.org`. matrix homeserver url. Default: `https://matrix.org`. Environment variable: `MATRIX_URL`
Environment variable: `MATRIX_URL`
-i MATRIX_ID, --matrix-id MATRIX_ID -i MATRIX_ID, --matrix-id MATRIX_ID
matrix user-id. Required. Environment variable: matrix user-id. Required. Environment variable: `MATRIX_ID`
`MATRIX_ID`
-p MATRIX_PW, --matrix-pw MATRIX_PW -p MATRIX_PW, --matrix-pw MATRIX_PW
matrix password. Required. Environment variable: matrix password. Required. Environment variable: `MATRIX_PW`
`MATRIX_PW` -k API_KEY, --api-key API_KEY
-k API_KEYS, --api-keys API_KEYS shared secret to use this service. Required. Environment variable: `API_KEY`
comma separated list of shared secrets to use this
service. Required. Environment variable: `API_KEYS`
-c CONFIG, --config CONFIG
configuration file. Default: `config.yaml`
-v, --verbose increment verbosity level -v, --verbose increment verbosity level
``` ```
@ -83,37 +79,17 @@ curl -d '{"body":"new contrib from toto: [44](http://radio.localhost/map/#44)",
``` ```
(or localhost:4785 without docker) (or localhost:4785 without docker)
### Formatters ### For Github
These formatters will output custom messages depending on the specific formatter. Generally to set these up, on the remote provider you would create a webhook with `https://your.webhook.domain/?formatter=<formatter columun below>&api_key=<your apikey>` Add a JSON webhook with `?formatter=github`, and put the `API_KEY` as secret
| formatter | description | key location | ### For Grafana
| -- | - | - |
| github | for github.com | in github JSON webhook settings as `secret` |
| grafana | for grafana | in webhook URL with `api_key=<yourkey>` |
| pingdom | for pingdom.com | in webhook URL with `api_key=<yourkey>` |
| buildbot | buildbot reporter | in webhook URL with `api_key=<yourkey>` or in master.cfg credentials header as `api_key` |
| generic | returns raw JSON that was recieved. For developing additional formatter plugins | in URL with api_key=<yourkey> |
For example, if your matrix-webhook was hosted at https://webhooks.example.com, and you were setting up pingdom and you have an api_key of "123", you would use the following URL for your webhook call from pingdom:
`https://webhooks.example.com/?formatter=pingdom&api_key=123`
### For Gitlab Add a webhook with an URL ending with `?formatter=grafana&key=API_KEY'
At a group level, Gitlab does not permit to setup webhooks. A workaround consists to use Google
Chat or Microsoft Teams notification integration with a custom URL (Gitlab does not check if the url begins with the normal url of the service).
#### Google Chat
Add a Google Chat integration with an URL ending with `?formatter=gitlab_gchat&key=API_KEY`
#### Microsoft Teams
Add a Microsoft Teams integration with an URL ending with `?formatter=gitlab_teams&key=API_KEY`
## Test room ## Test room
[#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org) [#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org?via=laas.fr&via=tetaneutral.net&via=aen.im)
## Unit tests ## Unit tests

View file

@ -1,19 +0,0 @@
hostname: localhost
port: 4785
# matrix-specific settings
matrix:
# URL of homeserver to connect
url: https://matrix.org
# user to connect to homserver as
id: username
# password for the user
pw: password
# keys to allow These should be random strings
# these could be generated with something like `openssl rand -hex 24`
# change these, you only need
api_keys:
RandomTextForKey: "!room_id:server.domain" # Can add a comment for what the key is used
secondRandomkey: "!a_different_room_id:server.domain"
thirdKey: #This one has no room specified, so it must be specified in the payload data or url
log:
level: debug

View file

@ -6,7 +6,6 @@ networks:
services: services:
bot: bot:
image: nim65s/matrix-webhook
build: . build: .
restart: unless-stopped restart: unless-stopped
env_file: env_file:

View file

@ -11,12 +11,10 @@ poetry version "$1"
NEW=$(poetry version -s) NEW=$(poetry version -s)
DATE=$(date +%Y-%m-%d) DATE=$(date +%Y-%m-%d)
sed -i "/^## \[Unreleased\]/a \\\n## [v$NEW] - $DATE" CHANGELOG.md sed -i "/^## \[Unreleased\]/a \\\n## [$NEW] - $DATE" CHANGELOG.md
sed -i "/^\[Unreleased\]/s/$OLD/$NEW/" CHANGELOG.md sed -i "/^\[Unreleased\]/s/$OLD/$NEW/" CHANGELOG.md
sed -i "/^\[Unreleased\]/a [v$NEW]: https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md sed -i "/^\[Unreleased\]/a [$NEW] https://github.com/nim65s/matrix-webhook/compare/v$OLD...v$NEW" CHANGELOG.md
git add pyproject.toml CHANGELOG.md git add pyproject.toml CHANGELOG.md
git commit -m "Release v$NEW" git commit -m "Release v$NEW"
git tag -s "v$NEW" -m "Release v$NEW" git tag -s "v$NEW" -m "Release v$NEW"
git push
git push --tags

View file

@ -3,13 +3,7 @@ import logging
from . import app, conf from . import app, conf
if __name__ == "__main__":
def main():
"""Start everything."""
log_format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s" log_format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s"
logging.basicConfig(level=50 - 10 * conf.VERBOSE, format=log_format) logging.basicConfig(level=50 - 10 * conf.VERBOSE, format=log_format)
app.run() app.run()
if __name__ == "__main__":
main()

View file

@ -1,25 +1,6 @@
"""Configuration for Matrix Webhook.""" """Configuration for Matrix Webhook."""
import argparse import argparse
import os import os
import sys
import yaml
def get_numeric_log_level(log_level):
"""Return a number that will calculate to the verbosity level"""
if log_level.lower() == "debug":
return 4
elif log_level.lower() == "info":
return 3
elif log_level.lower() == "warning":
return 2
elif log_level.lower() == "error":
return 1
elif log_level.lower() == "critical":
return 0
else:
return 2
parser = argparse.ArgumentParser(description=__doc__, prog="python -m matrix_webhook") parser = argparse.ArgumentParser(description=__doc__, prog="python -m matrix_webhook")
parser.add_argument( parser.add_argument(
@ -46,60 +27,41 @@ parser.add_argument(
"-i", "-i",
"--matrix-id", "--matrix-id",
help="matrix user-id. Required. Environment variable: `MATRIX_ID`", help="matrix user-id. Required. Environment variable: `MATRIX_ID`",
**(
{"default": os.environ["MATRIX_ID"]}
if "MATRIX_ID" in os.environ
else {"required": True}
),
) )
parser.add_argument( parser.add_argument(
"-p", "-p",
"--matrix-pw", "--matrix-pw",
help="matrix password. Required. Environment variable: `MATRIX_PW`", help="matrix password. Required. Environment variable: `MATRIX_PW`",
**(
{"default": os.environ["MATRIX_PW"]}
if "MATRIX_PW" in os.environ
else {"required": True}
),
) )
parser.add_argument( parser.add_argument(
"-k", "-k",
"--api-keys", "--api-key",
help="comma separated list of shared secrets to use this service. Required. Environment variable: `API_KEYS`", help="shared secret to use this service. Required. Environment variable: `API_KEY`",
**(
{"default": os.environ["API_KEY"]}
if "API_KEY" in os.environ
else {"required": True}
),
) )
parser.add_argument(
"-c",
"--config",
help="configuration file. Default: `config.yaml`",
)
parser.add_argument( parser.add_argument(
"-v", "--verbose", action="count", default=0, help="increment verbosity level" "-v", "--verbose", action="count", default=0, help="increment verbosity level"
) )
args = parser.parse_args() args = parser.parse_args()
if args.config: SERVER_ADDRESS = (args.host, args.port)
with open(args.config) as f: MATRIX_URL = args.matrix_url
config = yaml.safe_load(f) MATRIX_ID = args.matrix_id
SERVER_ADDRESS = (config["hostname"], config["port"]) MATRIX_PW = args.matrix_pw
MATRIX_URL = config["matrix"]["url"] API_KEY = args.api_key
MATRIX_ID = config["matrix"]["id"] VERBOSE = args.verbose
MATRIX_PW = config["matrix"]["pw"]
API_KEYS = config["api_keys"]
LOG_FILE = config["log"]
VERBOSE = get_numeric_log_level(config["log"]["level"])
else:
SERVER_ADDRESS = (args.host, args.port)
MATRIX_URL = args.matrix_url
LOG_FILE = args.log
if not args.matrix_id:
print("Missing matrix user-id. Use -i or --matrix-id or specify in config.yaml")
sys.exit(1)
else:
MATRIX_ID = args.matrix_id
if not args.matrix_pw:
print(
"Missing matrix password. Use -p or --matrix-pw or specify in config.yaml"
)
sys.exit(1)
else:
MATRIX_PW = args.matrix_pw
if not args.api_keys:
print("Missing api keys. Use -k or --api-keys or specify in config.yaml")
sys.exit(1)
else:
API_KEYS = args.api_keys.split(",")
VERBOSE = args.verbose

View file

@ -0,0 +1,32 @@
"""Formatters for matrix webhook."""
def grafana(data, headers):
"""Pretty-print a grafana notification."""
text = ""
if "title" in data:
text = "### " + data["title"] + "\n"
if "message" in data:
text = text + data["message"] + "\n\n"
if "evalMatches" in data:
for match in data["evalMatches"]:
text = text + "* " + match["metric"] + ": " + str(match["value"]) + "\n"
data["body"] = text
return data
def github(data, headers):
"""Pretty-print a github notification."""
# TODO: Write nice useful formatters. This is only an example.
if headers["X-GitHub-Event"] == "push":
pusher, ref, a, b, c = [
data[k] for k in ["pusher", "ref", "after", "before", "compare"]
]
pusher = f"[{pusher['name']}](https://github.com/{pusher['name']})"
data["body"] = f"@{pusher} pushed on {ref}: [{b}{a}]({c}):\n\n"
for commit in data["commits"]:
data["body"] += f"- [{commit['message']}]({commit['url']})\n"
else:
data["body"] = "notification from github"
data["digest"] = headers["X-Hub-Signature-256"].replace("sha256=", "")
return data

View file

@ -1,8 +0,0 @@
def formatter(data, headers):
"""Pretty-print an alertmanager notification."""
text = ""
for alert in data['alerts']:
text += f"[{alert['status']}] - {alert['labels']['summary']}\n\n"
data["body"] = text
return data

View file

@ -1,34 +0,0 @@
from datetime import datetime
def formatter(data, headers):
"""Pretty-print a buildbot notification."""
buildid = data["buildid"]
buildstate = data["state_string"]
buildlink = data["url"]
reason = data["buildset"]["reason"]
project = data["properties"]["project"][0]
submittime = datetime.fromtimestamp(data["buildset"]["submitted_at"])
try:
if buildstate == "starting":
data["body"] = (
f"###Buildbot job #{buildid} for {project} - {buildstate}\n\n"
f"{reason}\n\n"
f"**started at** {submittime}\n\n"
f"[view details]({buildlink})"
)
elif buildstate == "build successful":
data["body"] = (
f"###Buildbot job #{buildid} for {project} - {buildstate}\n\n"
f"**completed at** {datetime.fromtimestamp(data['complete_at'])}\n\n"
f"[view details]({buildlink})"
)
else:
data["body"] = (
f"###Buildbot job #{buildid} for {project} - {buildstate}\n\n"
f"[view details]({buildlink})"
)
except Exception as error:
print(error)
return data

View file

@ -1,36 +0,0 @@
import requests
def get_abuse_confidence(ip):
"""get abuseipdb's confidence level on an ip passed in, and return that value"""
base_url = "https://api.abuseipdb.com/api/v2/check"
api_key = "YOUR API KEY"
headers = {"Key": api_key, "Accept": "application/json"}
data = {"ipAddress": ip, "maxAgeInDays": 90}
r = requests.get(base_url, headers=headers, json=data)
confidence = r.json()["data"]["abuseConfidenceScore"]
whitelist = r.json()["data"]["isWhitelisted"]
return [confidence, whitelist]
def formatter(data, headers):
"""format a message sent with crowdsec http endpoints"""
data_out = ""
for row in data["body"]:
ip = row["host"]
duration = row["duration"]
confidence, whitelisted = get_abuse_confidence(ip)
if "crowdsecurity" in row["scenario"]:
source, scenario, *_ = row["scenario"].split("/")
row[
"scenario"
] = f"[{scenario}](https://hub.crowdsec.net/author/crowdsecurity/configurations/{scenario})"
data_out += f"{ip} has been banned {duration} due to {row['scenario']}\n\n"
if whitelisted:
data_out += "**Note: AbuseIPDB has whitelisted this address\n\n"
data_out += (
f"[AbuseIPDB](https://www.abuseipdb.com/check/{row['host']})({confidence}%) | "
f"[Crowdsec](https://app.crowdsec.net/cti/{row['host']})\n\n"
)
data["body"] = data_out
return data

View file

@ -1,4 +0,0 @@
def formatter(data, headers):
"""Just dump the json data"""
data["body"] = f"{data}"
return data

View file

@ -1,15 +0,0 @@
def formatter(data, headers):
"""Pretty-print a github notification."""
# TODO: Write nice useful formatters. This is only an example.
if headers["X-GitHub-Event"] == "push":
pusher, ref, a, b, c = [
data[k] for k in ["pusher", "ref", "after", "before", "compare"]
]
pusher = f"[@{pusher['name']}](https://github.com/{pusher['name']})"
data["body"] = f"{pusher} pushed on {ref}: [{b}{a}]({c}):\n\n"
for commit in data["commits"]:
data["body"] += f"- [{commit['message']}]({commit['url']})\n"
else:
data["body"] = "notification from github"
data["digest"] = headers["X-Hub-Signature-256"].replace("sha256=", "")
return data

View file

@ -1,12 +0,0 @@
def formatter(data, headers):
"""Pretty-print a grafana notification."""
text = ""
if "title" in data:
text = "#### " + data["title"] + "\n"
if "message" in data:
text = text + data["message"] + "\n\n"
if "evalMatches" in data:
for match in data["evalMatches"]:
text = text + "* " + match["metric"] + ": " + str(match["value"]) + "\n"
data["body"] = text
return data

View file

@ -1,9 +0,0 @@
def grafana_9x(data, headers):
"""Pretty-print a Grafana newer than v9.x notification."""
text = ""
if "title" in data:
text = "#### " + data["title"] + "\n"
if "message" in data:
text = text + data["message"].replace("\n", "\n\n") + "\n\n"
data["body"] = text
return data

View file

@ -1,64 +0,0 @@
from datetime import datetime
def formatter(data, headers):
"""Pretty-print a pingdom notification."""
# JSON data formatting was obtained from https://www.pingdom.com/resources/webhooks/
# these are common to all check types
check_id = data["check_id"]
check_name = data["check_name"]
current_state = data["current_state"]
tags = data["tags"]
local_time = datetime.fromtimestamp(data["state_changed_timestamp"])
if data["check_type"].lower() == "http":
# http https or http_custom check types
try:
check_url = data["check_params"]["full_url"]
message = ""
message += f"###{check_name} is {current_state}\n\n{check_url}"
message += f" marked {current_state} at {local_time}"
message += f"[view details](https://my.pingdom.com/reports/responsetime#check={check_id})"
if tags:
message += f"\n\nTags: {tags}"
data["body"] = message
except Exception as error:
data["body"] = (
f"Error: An attempt to post from pingdom was malformed "
"(or I don't know how to handle what was sent).\n\n"
f"{repr(error)}"
)
elif data["check_type"].lower() == "dns":
# There are a bunch of values that are blanke when you do a test
# so ignore them if value is unset
try:
first_ip = data["first_probe"]["ip"]
except KeyError:
first_ip = "unknown"
try:
second_ip = data["second_probe"]["ip"]
except KeyError:
second_ip = "unknown"
try:
first_location = data["first_probe"]["location"]
except KeyError:
first_location = "unknown"
try:
second_location = data["second_probe"]["location"]
except KeyError:
second_location = "unknown"
try:
expected_ip = data["check_params"]["expected_ip"]
data["body"] = (
f"###{check_name} is {current_state}\n\n"
f"expected {expected_ip} but got:\n\n"
f" {first_ip} ({first_location})\n\n"
f" {second_ip} ({second_location})\n\n"
f" marked {current_state} at {local_time}"
f"[view details](https://my.pingdom.com/reports/responsetime#check={check_id})"
)
except Exception as error:
print(error)
return data

View file

@ -1,5 +0,0 @@
def formatter(data, headers):
""" format a message sent with slack api endpoints"""
text = data["attachments"][0]["text"]
data["body"] = f"{text}"
return data

View file

@ -4,11 +4,10 @@ import json
import logging import logging
from http import HTTPStatus from http import HTTPStatus
from hmac import HMAC from hmac import HMAC
import importlib
from markdown import markdown from markdown import markdown
from . import conf, utils from . import conf, formatters, utils
LOGGER = logging.getLogger("matrix_webhook.handler") LOGGER = logging.getLogger("matrix_webhook.handler")
@ -37,10 +36,10 @@ async def matrix_webhook(request):
if "formatter" in request.rel_url.query: if "formatter" in request.rel_url.query:
try: try:
format = request.rel_url.query["formatter"] data = getattr(formatters, request.rel_url.query["formatter"])(
plugin = importlib.import_module(f"matrix_webhook.formatters.{format}", "formatter") data, request.headers
data = plugin.formatter(data, request.headers) )
except ModuleNotFoundError: except AttributeError:
return utils.create_json_response( return utils.create_json_response(
HTTPStatus.BAD_REQUEST, "Unknown formatter" HTTPStatus.BAD_REQUEST, "Unknown formatter"
) )
@ -69,7 +68,7 @@ async def matrix_webhook(request):
HTTPStatus.BAD_REQUEST, f"Missing {', '.join(missing)}" HTTPStatus.BAD_REQUEST, f"Missing {', '.join(missing)}"
) )
if data["key"] not in conf.API_KEYS: if data["key"] != conf.API_KEY:
return utils.create_json_response(HTTPStatus.UNAUTHORIZED, "Invalid API key") return utils.create_json_response(HTTPStatus.UNAUTHORIZED, "Invalid API key")
if "formatted_body" in data: if "formatted_body" in data:
@ -77,11 +76,6 @@ async def matrix_webhook(request):
else: else:
formatted_body = markdown(str(data["body"]), extensions=["extra"]) formatted_body = markdown(str(data["body"]), extensions=["extra"])
# try to join room first -> non none response means error
resp = await utils.join_room(data["room_id"])
if resp is not None:
return resp
content = { content = {
"msgtype": "m.text", "msgtype": "m.text",
"body": data["body"], "body": data["body"],

View file

@ -6,27 +6,15 @@ from http import HTTPStatus
from aiohttp import web from aiohttp import web
from nio import AsyncClient from nio import AsyncClient
from nio.exceptions import LocalProtocolError from nio.exceptions import LocalProtocolError
from nio.responses import RoomSendError, JoinError from nio.responses import RoomSendError
from . import conf from . import conf
ERROR_MAP = { ERROR_MAP = {"M_FORBIDDEN": HTTPStatus.FORBIDDEN}
"M_FORBIDDEN": HTTPStatus.FORBIDDEN,
"M_CONSENT_NOT_GIVEN": HTTPStatus.FORBIDDEN,
}
LOGGER = logging.getLogger("matrix_webhook.utils") LOGGER = logging.getLogger("matrix_webhook.utils")
CLIENT = AsyncClient(conf.MATRIX_URL, conf.MATRIX_ID) CLIENT = AsyncClient(conf.MATRIX_URL, conf.MATRIX_ID)
def error_map(resp):
"""Map response errors to HTTP status."""
if resp.status_code == "M_UNKNOWN":
# in this case, we should directly consider the HTTP status from the response
# ref. https://matrix.org/docs/spec/client_server/r0.6.1#api-standards
return resp.transport_response.status
return ERROR_MAP[resp.status_code]
def create_json_response(status, ret): def create_json_response(status, ret):
"""Create a JSON response.""" """Create a JSON response."""
LOGGER.debug(f"Creating json response: {status=}, {ret=}") LOGGER.debug(f"Creating json response: {status=}, {ret=}")
@ -34,27 +22,6 @@ def create_json_response(status, ret):
return web.json_response(response_data, status=status) return web.json_response(response_data, status=status)
async def join_room(room_id):
"""Try to join the room."""
LOGGER.debug(f"Join room {room_id=}")
for _ in range(10):
try:
resp = await CLIENT.join(room_id)
if isinstance(resp, JoinError):
if resp.status_code == "M_UNKNOWN_TOKEN":
LOGGER.warning("Reconnecting")
await CLIENT.login(conf.MATRIX_PW)
else:
return create_json_response(error_map(resp), resp.message)
else:
return None
except LocalProtocolError as e:
LOGGER.error(f"Send error: {e}")
LOGGER.warning("Trying again")
return create_json_response(HTTPStatus.GATEWAY_TIMEOUT, "Homeserver not responding")
async def send_room_message(room_id, content): async def send_room_message(room_id, content):
"""Send a message to a room.""" """Send a message to a room."""
LOGGER.debug(f"Sending room message in {room_id=}: {content=}") LOGGER.debug(f"Sending room message in {room_id=}: {content=}")
@ -69,7 +36,9 @@ async def send_room_message(room_id, content):
LOGGER.warning("Reconnecting") LOGGER.warning("Reconnecting")
await CLIENT.login(conf.MATRIX_PW) await CLIENT.login(conf.MATRIX_PW)
else: else:
return create_json_response(error_map(resp), resp.message) return create_json_response(
ERROR_MAP[resp.status_code], resp.message
)
else: else:
return create_json_response(HTTPStatus.OK, "OK") return create_json_response(HTTPStatus.OK, "OK")
except LocalProtocolError as e: except LocalProtocolError as e:

2577
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +0,0 @@
{
"dependencies": {
"@mermaid-js/mermaid-cli": "^8.13.3"
}
}

1014
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "matrix-webhook" name = "matrix-webhook"
version = "3.5.0" version = "3.2.0"
description = "Post a message to a matrix room with a simple HTTP POST" description = "Post a message to a matrix room with a simple HTTP POST"
authors = ["Guilhem Saurel <guilhem.saurel@laas.fr>"] authors = ["Guilhem Saurel <guilhem.saurel@laas.fr>"]
license = "BSD-2-Clause" license = "BSD-2-Clause"
@ -8,22 +8,17 @@ readme = "README.md"
homepage = "https://github.com/nim65s/matrix-webhook" homepage = "https://github.com/nim65s/matrix-webhook"
repository = "https://github.com/nim65s/matrix-webhook.git" repository = "https://github.com/nim65s/matrix-webhook.git"
[tool.poetry.urls]
"changelog" = "https://github.com/nim65s/matrix-webhook/blob/master/CHANGELOG.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.8"
Markdown = "^3.3.4" Markdown = "^3.3.4"
matrix-nio = ">=0.18.3,<0.21.0" matrix-nio = "^0.18.3"
PyYAML = "^6.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
httpx = "^0.23.0" httpx = "^0.18.2"
black = "^22.8.0" coverage = "^5.5"
coverage = "^6.4.4" black = "^21.6b0"
pydocstyle = "^6.1.1" pydocstyle = "^6.1.1"
flake8 = "^5.0.4" flake8 = "^3.9.2"
pyupgrade = "^2.31.0"
[tool.pydocstyle] [tool.pydocstyle]
ignore = ["D200", "D203", "D204", "D212"] ignore = ["D200", "D203", "D204", "D212"]
@ -31,6 +26,3 @@ ignore = ["D200", "D203", "D204", "D212"]
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
matrix-webhook = "matrix_webhook.__main__:main"

View file

@ -11,22 +11,6 @@ WORKDIR $SYNAPSE_CONFIG_DIR
RUN chown -R 991:991 . \ RUN chown -R 991:991 . \
&& /start.py generate \ && /start.py generate \
&& sed -i 's=/data=/srv=;s=8008=80=;s=#sup=sup=;' homeserver.yaml \ && sed -i 's=/data=/srv=;s=8008=80=;s=#sup=sup=;' homeserver.yaml \
&& echo "" >> homeserver.yaml \
&& echo "rc_message:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& echo "rc_registration:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& echo "rc_registration_token_validity:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& echo "rc_login:" >> homeserver.yaml \
&& echo " address:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& echo " account:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& echo " failed_attempts:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& echo "rc_joins:" >> homeserver.yaml \
&& echo " burst_count: 1000" >> homeserver.yaml \
&& python -m synapse.app.homeserver --config-path homeserver.yaml --generate-keys && python -m synapse.app.homeserver --config-path homeserver.yaml --generate-keys
RUN pip install --no-cache-dir markdown matrix-nio httpx coverage RUN pip install --no-cache-dir markdown matrix-nio httpx coverage

View file

@ -1 +0,0 @@
{"text":"John Doe pushed to branch \u003chttps://gitlab.com/jdoe/test/commits/master|master\u003e of \u003chttps://gitlab.com/jdoe/test|John Doe / test\u003e (\u003chttps://gitlab.com/jdoe/test/compare/b76004b20503d4d506e51a670de095cc063e4707...3517b06c64c9d349e2213650d6c009db0471361e|Compare changes\u003e)\n\u003chttps://gitlab.com/jdoe/test/-/commit/3517b06c64c9d349e2213650d6c009db0471361e|3517b06c\u003e: Merge branch 'prod' into 'master' - John Doe\n\n\u003chttps://gitlab.com/jdoe/test/-/commit/1f661795b220c5fe352f391eb8de3ac4fcc6fc1d|1f661795\u003e: Merge branch 'revert-a827b196' into 'prod' - John Doe\n\n\u003chttps://gitlab.com/jdoe/test/-/commit/b76004b20503d4d506e51a670de095cc063e4707|b76004b2\u003e: Merge branch 'revert-a827b196' into 'master' - John Doe"}

View file

@ -1 +0,0 @@
{"sections":[{"activityTitle":"John Doe pushed to branch [master](https://gitlab.com/jdoe/test/commits/master)","activitySubtitle":"in [John Doe / test](https://gitlab.com/jdoe/test)","activityText":"[Compare changes](https://gitlab.com/jdoe/test/compare/b76004b20503d4d506e51a670de095cc063e4707...3517b06c64c9d349e2213650d6c009db0471361e)","activityImage":"https://secure.gravatar.com/avatar/80\u0026d=identicon"},{"text":"[3517b06c](https://gitlab.com/jdoe/test/-/commit/3517b06c64c9d349e2213650d6c009db0471361e): Merge branch 'prod' into 'master' - John Doe\n\n[1f661795](https://gitlab.com/jdoe/test/-/commit/1f661795b220c5fe352f391eb8de3ac4fcc6fc1d): Merge branch 'revert-a827b196' into 'prod' - John Doe\n\n[b76004b2](https://gitlab.com/jdoe/test/-/commit/b76004b20503d4d506e51a670de095cc063e4707): Merge branch 'revert-a827b196' into 'master' - John Doe"}],"title":"John Doe / test","summary":"John Doe pushed to branch [master](https://gitlab.com/jdoe/test/commits/master) of [John Doe / test](https://gitlab.com/jdoe/test) ([Compare changes](https://gitlab.com/jdoe/test/compare/b76004b20503d4d506e51a670de095cc063e4707...3517b06c64c9d349e2213650d6c009db0471361e))"}

View file

@ -1,41 +0,0 @@
{
"receiver": "",
"status": "firing",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "TestAlert",
"instance": "Grafana"
},
"annotations": {
"summary": "Notification test"
},
"startsAt": "2022-09-07T15:00:26.722304913+02:00",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "",
"fingerprint": "57c6d9296de2ad39",
"silenceURL": "https://grafana.example.com/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTestAlert&matcher=instance%3DGrafana",
"dashboardURL": "",
"panelURL": "",
"valueString": "[ metric='foo' labels={instance=bar} value=10 ]"
}
],
"groupLabels": {},
"commonLabels": {
"alertname": "TestAlert",
"instance": "Grafana"
},
"commonAnnotations": {
"summary": "Notification test"
},
"externalURL": "https://grafana.example.com/",
"version": "1",
"groupKey": "{alertname=\"TestAlert\", instance=\"Grafana\"}2022-09-07 15:00:26.722304913 +0200 CEST m=+246580.963796811",
"truncatedAlerts": 0,
"orgId": 1,
"title": "[FIRING:1] (TestAlert Grafana)",
"state": "alerting",
"message": "**Firing**\n\nValue: [ metric='foo' labels={instance=bar} value=10 ]\nLabels:\n - alertname = TestAlert\n - instance = Grafana\nAnnotations:\n - summary = Notification test\nSilence: https://grafana.example.com/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTestAlert&matcher=instance%3DGrafana\n",
"key": "ak"
}

View file

@ -1,33 +0,0 @@
{
"check_id": 12345,
"check_name": "Pingdom-dns build test",
"check_type": "DNS",
"check_params": {
"hostname": "www.example.com",
"basic_auth": false,
"expected_ip": "123.4.5.6",
"ipv6": false,
"nameserver": "example.com"
},
"tags": [
"example_tag"
],
"previous_state": "UP",
"current_state": "DOWN",
"importance_level": "HIGH",
"state_changed_timestamp": 1451610061,
"state_changed_utc_time": "2016-01-01T01:01:01",
"long_description": "Long error message",
"description": "Short error message",
"first_probe": {
"ip": "123.4.5.6",
"ipv6": "2001:4800:1020:209::5",
"location": "Stockholm, Sweden"
},
"second_probe": {
"ip": "123.4.5.6",
"ipv6": "2001:4800:1020:209::5",
"location": "Austin, US",
"version": 1
}
}

View file

@ -1,36 +0,0 @@
{
"check_id": 12345,
"check_name": "Pingdom-http build test",
"check_type": "HTTP",
"check_params": {
"basic_auth": false,
"encryption": true,
"full_url": "https://www.example.com/path",
"header": "User-Agent:Pingdom.com_bot",
"hostname": "www.example.com",
"ipv6": false,
"port": 443,
"url": "/path"
},
"tags": [
"example_tag"
],
"previous_state": "UP",
"current_state": "DOWN",
"importance_level": "HIGH",
"state_changed_timestamp": 1451610061,
"state_changed_utc_time": "2016-01-01T01:01:01",
"long_description": "Long error message",
"description": "Short error message",
"first_probe": {
"ip": "123.4.5.6",
"ipv6": "2001:4800:1020:209::5",
"location": "Stockholm, Sweden"
},
"second_probe": {
"ip": "123.4.5.6",
"ipv6": "2001:4800:1020:209::5",
"location": "Austin, US",
"version": 1
}
}

View file

@ -1,34 +0,0 @@
{
"body":{
"check_id": 12345,
"check_name": "Pingdom-tcp build test",
"check_type": "PORT_TCP",
"check_params": {
"hostname": "www.example.com",
"basic_auth": false,
"ipv6": false,
"port": 80
},
"tags": [
"example_tag"
],
"previous_state": "UP",
"current_state": "DOWN",
"importance_level": "HIGH",
"state_changed_timestamp": 1451610061,
"state_changed_utc_time": "2016-01-01T01:01:01",
"long_description": "Long error message",
"description": "Short error message",
"first_probe": {
"ip": "123.4.5.6",
"ipv6": "2001:4800:1020:209::5",
"location": "Stockholm, Sweden"
},
"second_probe": {
"ip": "123.4.5.6",
"ipv6": "2001:4800:1020:209::5",
"location": "Austin, US",
"version": 1
}
}
}

View file

@ -93,7 +93,7 @@ class GithubFormatterTest(unittest.IsolatedAsyncioTestCase):
before = "ac7d1d9647008145e9d0cf65d24744d0db4862b8" before = "ac7d1d9647008145e9d0cf65d24744d0db4862b8"
after = "4bcdb25c809391baaabc264d9309059f9f48ead2" after = "4bcdb25c809391baaabc264d9309059f9f48ead2"
GH = "https://github.com" GH = "https://github.com"
expected = f'<p><a href="{GH}/nim65s">@nim65s</a> pushed on refs/heads/devel: ' expected = f'<p>@<a href="{GH}/nim65s">nim65s</a> pushed on refs/heads/devel: '
expected += f'<a href="{GH}/nim65s/matrix-webhook/compare/ac7d1d964700...' expected += f'<a href="{GH}/nim65s/matrix-webhook/compare/ac7d1d964700...'
expected += f'4bcdb25c8093">{before}{after}</a>:</p>\n<ul>\n<li>' expected += f'4bcdb25c8093">{before}{after}</a>:</p>\n<ul>\n<li>'
expected += f'<a href="{GH}/nim65s/matrix-webhook/commit/{after}">' expected += f'<a href="{GH}/nim65s/matrix-webhook/commit/{after}">'
@ -127,4 +127,3 @@ class GithubFormatterTest(unittest.IsolatedAsyncioTestCase):
).json(), ).json(),
{"status": 401, "ret": "Invalid SHA-256 HMAC digest"}, {"status": 401, "ret": "Invalid SHA-256 HMAC digest"},
) )
await client.close()

View file

@ -1,54 +0,0 @@
"""
Test module for gitlab "google chat" formatter.
"""
import unittest
import httpx
import nio
from .start import BOT_URL, FULL_ID, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL
class GitlabGchatFormatterTest(unittest.IsolatedAsyncioTestCase):
"""Gitlab "google chat" formatter test class."""
async def test_gitlab_gchat_body(self):
"""Send a markdown message, and check the result."""
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
with open("tests/example_gitlab_gchat.json") as f:
example_gitlab_gchat_request = f.read()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={"formatter": "gitlab_gchat", "key": KEY},
content=example_gitlab_gchat_request,
).json(),
{"status": 200, "ret": "OK"},
)
sync = await client.sync()
messages = await client.room_messages(room.room_id, sync.next_batch)
await client.close()
message = messages.chunk[0]
self.assertEqual(message.sender, FULL_ID)
self.assertEqual(
message.body,
"John Doe pushed to branch [master](https://gitlab.com/jdoe/test/commits/m"
+ "aster) of [John Doe / test](https://gitlab.com/jdoe/test) ([Compare chan"
+ "ges](https://gitlab.com/jdoe/test/compare/b76004b20503d4d506e51a670de095"
+ "cc063e4707...3517b06c64c9d349e2213650d6c009db0471361e))\n[3517b06c](http"
+ "s://gitlab.com/jdoe/test/-/commit/3517b06c64c9d349e2213650d6c009db047136"
+ "1e): Merge branch 'prod' into 'master' - John Doe\n\n[1f661795](https://"
+ "gitlab.com/jdoe/test/-/commit/1f661795b220c5fe352f391eb8de3ac4fcc6fc1d):"
+ " Merge branch 'revert-a827b196' into 'prod' - John Doe\n\n[b76004b2](htt"
+ "ps://gitlab.com/jdoe/test/-/commit/b76004b20503d4d506e51a670de095cc063e4"
+ "707): Merge branch 'revert-a827b196' into 'master' - John Doe",
)

View file

@ -1,55 +0,0 @@
"""
Test module for gitlab "teams" formatter.
"""
import unittest
import httpx
import nio
from .start import BOT_URL, FULL_ID, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL
class GitlabTeamsFormatterTest(unittest.IsolatedAsyncioTestCase):
"""Gitlab "teams" formatter test class."""
async def test_gitlab_teams_body(self):
"""Send a markdown message, and check the result."""
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
with open("tests/example_gitlab_teams.json") as f:
example_gitlab_teams_request = f.read()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={"formatter": "gitlab_teams", "key": KEY},
content=example_gitlab_teams_request,
).json(),
{"status": 200, "ret": "OK"},
)
sync = await client.sync()
messages = await client.room_messages(room.room_id, sync.next_batch)
await client.close()
message = messages.chunk[0]
self.assertEqual(message.sender, FULL_ID)
self.assertEqual(
message.body,
"John Doe pushed to branch [master](https://gitlab.com/jdoe/test/commits"
+ "/master) in [John Doe / test](https://gitlab.com/jdoe/test) \u2192 [Com"
+ "pare changes](https://gitlab.com/jdoe/test/compare/b76004b20503d4d506e5"
+ "1a670de095cc063e4707...3517b06c64c9d349e2213650d6c009db0471361e) \n\n*"
+ " [3517b06c](https://gitlab.com/jdoe/test/-/commit/3517b06c64c9d349e2213"
+ "650d6c009db0471361e): Merge branch 'prod' into 'master' - John Doe \n*"
+ " [1f661795](https://gitlab.com/jdoe/test/-/commit/1f661795b220c5fe352f3"
+ "91eb8de3ac4fcc6fc1d): Merge branch 'revert-a827b196' into 'prod' - John"
+ " Doe \n* [b76004b2](https://gitlab.com/jdoe/test/-/commit/b76004b20503"
+ "d4d506e51a670de095cc063e4707): Merge branch 'revert-a827b196' into 'mas"
+ "ter' - John Doe",
)

View file

@ -42,5 +42,5 @@ class GrafanaFormatterTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(message.sender, FULL_ID) self.assertEqual(message.sender, FULL_ID)
self.assertEqual( self.assertEqual(
message.body, message.body,
"#### [Alerting] Panel Title alert\nNotification Message\n\n* Count: 1\n", "### [Alerting] Panel Title alert\nNotification Message\n\n* Count: 1\n",
) )

View file

@ -1,51 +0,0 @@
"""
Test module for grafana v9 formatter.
ref https://grafana.com/docs/grafana/latest/alerting/old-alerting/notifications/#webhook
"""
import unittest
import httpx
import nio
from .start import BOT_URL, FULL_ID, MATRIX_ID, MATRIX_PW, MATRIX_URL
class Grafana9xFormatterTest(unittest.IsolatedAsyncioTestCase):
"""Grafana formatter test class."""
async def test_grafana_body(self):
"""Send a markdown message, and check the result."""
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
with open("tests/example_grafana_9x.json") as f:
example_grafana_request = f.read()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={"formatter": "grafana_9x"},
content=example_grafana_request,
).json(),
{"status": 200, "ret": "OK"},
)
sync = await client.sync()
messages = await client.room_messages(room.room_id, sync.next_batch)
await client.close()
message = messages.chunk[0]
self.assertEqual(message.sender, FULL_ID)
expected_body = (
"#### [FIRING:1] (TestAlert Grafana)\n**Firing**\n\n\n\nValue: [ metr"
"ic='foo' labels={instance=bar} value=10 ]\n\nLabels:\n\n - alertname "
"= TestAlert\n\n - instance = Grafana\n\nAnnotations:\n\n - summary = "
"Notification test\n\nSilence: https://grafana.example.com/alerting/si"
"lence/new?alertmanager=grafana&matcher=alertname%3DTestAlert&matcher="
"instance%3DGrafana\n\n\n\n"
)
self.assertEqual(message.body, expected_body)

View file

@ -1,51 +0,0 @@
"""
Test version 9 compatibility of grafana formatter.
ref https://grafana.com/docs/grafana/latest/alerting/old-alerting/notifications/#webhook
"""
import unittest
import httpx
import nio
from .start import BOT_URL, FULL_ID, MATRIX_ID, MATRIX_PW, MATRIX_URL
class GrafanaForwardFormatterTest(unittest.IsolatedAsyncioTestCase):
"""Grafana formatter test class."""
async def test_grafana_body(self):
"""Send a markdown message, and check the result."""
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
with open("tests/example_grafana_9x.json") as f:
example_grafana_request = f.read()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={"formatter": "grafana"},
content=example_grafana_request,
).json(),
{"status": 200, "ret": "OK"},
)
sync = await client.sync()
messages = await client.room_messages(room.room_id, sync.next_batch)
await client.close()
message = messages.chunk[0]
self.assertEqual(message.sender, FULL_ID)
expected_body = (
"#### [FIRING:1] (TestAlert Grafana)\n**Firing**\n\n\n\nValue: [ metr"
"ic='foo' labels={instance=bar} value=10 ]\n\nLabels:\n\n - alertname "
"= TestAlert\n\n - instance = Grafana\n\nAnnotations:\n\n - summary = "
"Notification test\n\nSilence: https://grafana.example.com/alerting/si"
"lence/new?alertmanager=grafana&matcher=alertname%3DTestAlert&matcher="
"instance%3DGrafana\n\n\n\n"
)
self.assertEqual(message.body, expected_body)

View file

@ -1,44 +0,0 @@
"""
Test module for pingdom formatter.
"""
import unittest
import httpx
import nio
from .start import BOT_URL, FULL_ID, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL
class PingdomFormatterTest(unittest.IsolatedAsyncioTestCase):
"""Grafana formatter test class."""
async def test_pingdom_http_body(self):
"""Send a markdown message, and check the result."""
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
with open("tests/example_pingdom_http.json") as f:
example_pingdom_request = f.read()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={"formatter": "pingdom", "key": KEY},
content=example_pingdom_request,
).json(),
{"status": 200, "ret": "OK"},
)
sync = await client.sync()
messages = await client.room_messages(room.room_id, sync.next_batch)
await client.close()
message = messages.chunk[0]
self.assertEqual(message.sender, FULL_ID)
self.assertEqual(
message.body,
"#### [Alerting] Panel Title alert\nNotification Message\n\n* Count: 1\n",
)

View file

@ -33,11 +33,11 @@ class BotTest(unittest.IsolatedAsyncioTestCase):
# this won't be a 403 from synapse, but a LocalProtocolError from matrix_webhook # this won't be a 403 from synapse, but a LocalProtocolError from matrix_webhook
self.assertEqual( self.assertEqual(
bot_req({"body": 3}, KEY, "wrong_room"), bot_req({"body": 3}, KEY, "wrong_room"),
{"status": 400, "ret": "wrong_room was not legal room ID or room alias"}, {"status": 403, "ret": "Unknown room"},
) )
self.assertEqual( self.assertEqual(
bot_req({"body": 3}, KEY, "wrong_room", key_as_param=True), bot_req({"body": 3}, KEY, "wrong_room", key_as_param=True),
{"status": 400, "ret": "wrong_room was not legal room ID or room alias"}, {"status": 403, "ret": "Unknown room"},
) )
async def test_message(self): async def test_message(self):