Compare commits

...

232 commits

Author SHA1 Message Date
bca1d919b2 add alertmanager formatter 2025-01-21 10:26:07 -05:00
8e1a0894f3 docs: updates to config-dist.yaml 2023-01-17 14:11:28 -05:00
86f49edf3d docs: basic readme change from the upstream version 2023-01-17 13:36:50 -05:00
fc1552304f add whitelist check to AbuseIPDB check 2022-12-08 20:29:01 -05:00
66df6e56e2 Add abuseipdb lookup to link, fix differences in version running in prod 2022-12-02 18:03:25 -05:00
05eeab7c9c add abuseipdb and crowdsec cti links for IP 2022-12-02 11:20:30 -05:00
33a650efda remove github stuff that I didn't set up for my fork 2022-11-29 22:25:51 -05:00
eb0231f4ba add pyyaml dep 2022-11-29 22:22:30 -05:00
7ff165a6f7 add crowdsec formatter 2022-11-29 22:12:42 -05:00
3c2b2d346c add package*.json 2022-11-29 22:11:08 -05:00
6e05a2a3f9 merge upstream formatter for grafana_9x 2022-11-29 22:09:11 -05:00
6781f994f9 merge upstream changes 2022-11-29 22:08:43 -05:00
Guilhem Saurel
7ba365d6e8
Merge pull request #54 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-11-29 09:03:21 +01:00
pre-commit-ci[bot]
347f639422
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0)
- [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0)
2022-11-28 20:10:31 +00:00
Guilhem Saurel
e8fcb07205
Merge pull request #52 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-11-14 21:20:49 +01:00
pre-commit-ci[bot]
6cc4a7cfd9
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2)
2022-11-14 19:25:31 +00:00
Guilhem Saurel
e963478986
Merge pull request #51 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-10-31 22:25:26 +01:00
pre-commit-ci[bot]
2ac1f123fd
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0)
2022-10-31 19:21:12 +00:00
Guilhem Saurel
37def41cc8
Merge pull request #50 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-10-17 21:34:28 +02:00
pre-commit-ci[bot]
1dc9d79a3b
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.0.0 → v3.1.0](https://github.com/asottile/pyupgrade/compare/v3.0.0...v3.1.0)
2022-10-17 19:11:40 +00:00
Guilhem Saurel
07f3bd4714 fix ci cache 2022-10-17 10:40:09 +02:00
Guilhem Saurel
3017fb255a ci: cache poetry 2022-10-17 10:20:23 +02:00
Guilhem Saurel
97b842a20e
Merge pull request #49 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-10-10 22:10:05 +02:00
pre-commit-ci[bot]
962812758b
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0)
- [github.com/asottile/pyupgrade: v2.38.2 → v3.0.0](https://github.com/asottile/pyupgrade/compare/v2.38.2...v3.0.0)
2022-10-10 19:35:08 +00:00
Guilhem Saurel
67ffd334e3 poetry update 2022-10-01 09:56:47 +02:00
Guilhem Saurel
5140df64c6
Merge pull request #48 from nim65s/dependabot/pip/matrix-nio-0.20.0
build(deps): bump matrix-nio from 0.18.7 to 0.20.0
2022-10-01 09:44:51 +02:00
dependabot[bot]
eb28e99aa9
build(deps): bump matrix-nio from 0.18.7 to 0.20.0
Bumps [matrix-nio](https://github.com/poljar/matrix-nio) from 0.18.7 to 0.20.0.
- [Release notes](https://github.com/poljar/matrix-nio/releases)
- [Changelog](https://github.com/poljar/matrix-nio/blob/main/CHANGELOG.md)
- [Commits](https://github.com/poljar/matrix-nio/compare/0.18.7...0.20.0)

---
updated-dependencies:
- dependency-name: matrix-nio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-30 22:59:18 +00:00
Guilhem Saurel
c251d1c9cb
Merge pull request #47 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-09-27 11:14:27 +02:00
pre-commit-ci[bot]
8fac0e1a5d
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.38.0 → v2.38.2](https://github.com/asottile/pyupgrade/compare/v2.38.0...v2.38.2)
2022-09-26 19:09:45 +00:00
Guilhem Saurel
272a427034
Merge pull request #46 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-09-21 14:58:15 +02:00
pre-commit-ci[bot]
7915e78d87
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.37.3 → v2.38.0](https://github.com/asottile/pyupgrade/compare/v2.37.3...v2.38.0)
2022-09-19 19:10:39 +00:00
Guilhem Saurel
efdfd3a2db Release v3.5.0 2022-09-07 18:45:41 +02:00
Guilhem Saurel
5b7067f382 bump some tools 2022-09-07 18:20:59 +02:00
Guilhem Saurel
228f38f101 poetry update 2022-09-07 18:18:34 +02:00
Guilhem Saurel
1fb1a0b8f3 add #45 to CHANGELOG 2022-09-07 18:16:37 +02:00
Guilhem Saurel
880832275b
Merge pull request #45 from svenseeberg/bugfix/grafana-9x-formatting
Support Grafana v9.x messages, fixes #29
2022-09-07 18:14:41 +02:00
Sven Seeberg
e139c3a61b
Support Grafana v9.x messages, fixes #29 2022-09-07 17:45:32 +02:00
Guilhem Saurel
3288c3ff5a
Merge pull request #44 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-09-05 21:49:37 +02:00
pre-commit-ci[bot]
48b988efc0
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.6.0 → 22.8.0](https://github.com/psf/black/compare/22.6.0...22.8.0)
2022-09-05 19:11:44 +00:00
Guilhem Saurel
8f3a6a77ff
Merge pull request #43 from nim65s/dependabot/github_actions/codecov/codecov-action-3
build(deps): bump codecov/codecov-action from 1 to 3
2022-08-15 12:33:03 +02:00
dependabot[bot]
d25cfd3ba6
build(deps): bump codecov/codecov-action from 1 to 3
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 1 to 3.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v1...v3)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-15 10:28:23 +00:00
Guilhem Saurel
bc858f8bac Release v3.4.0 2022-08-12 11:50:09 +02:00
Guilhem Saurel
13c7d0a431 add changelog to urls 2022-08-12 11:47:48 +02:00
Guilhem Saurel
c82ab8c5e0 docker: setup qemu & buildx 2022-08-12 11:46:01 +02:00
Guilhem Saurel
53dd300414 update changelog 2022-08-12 11:45:42 +02:00
Guilhem Saurel
1750a1fbed
Merge pull request #42 from nim65s/dependabot/github_actions/actions/checkout-3
build(deps): bump actions/checkout from 2 to 3
2022-08-12 11:41:56 +02:00
dependabot[bot]
807a1e7b99
build(deps): bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-12 09:39:52 +00:00
Guilhem Saurel
8632c87d4f
Merge pull request #39 from nim65s/dependabot/github_actions/actions/setup-python-4
build(deps): bump actions/setup-python from 2 to 4
2022-08-12 11:39:43 +02:00
Guilhem Saurel
5905136919
Merge pull request #41 from nim65s/dependabot/github_actions/docker/login-action-2
build(deps): bump docker/login-action from 1 to 2
2022-08-12 11:39:30 +02:00
Guilhem Saurel
776ebb67c2
Merge pull request #40 from nim65s/dependabot/github_actions/docker/metadata-action-4
build(deps): bump docker/metadata-action from 3 to 4
2022-08-12 11:39:26 +02:00
Guilhem Saurel
0ef10cc620
Merge pull request #38 from nim65s/dependabot/github_actions/docker/build-push-action-3
build(deps): bump docker/build-push-action from 2 to 3
2022-08-12 11:39:19 +02:00
dependabot[bot]
93211f33c7
build(deps): bump docker/login-action from 1 to 2
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-12 09:35:26 +00:00
dependabot[bot]
7e3983626d
build(deps): bump docker/metadata-action from 3 to 4
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 3 to 4.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Upgrade guide](https://github.com/docker/metadata-action/blob/master/UPGRADE.md)
- [Commits](https://github.com/docker/metadata-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-12 09:35:23 +00:00
dependabot[bot]
8127e52fc7
build(deps): bump actions/setup-python from 2 to 4
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 4.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2...v4)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-12 09:35:20 +00:00
dependabot[bot]
d49bd60403
build(deps): bump docker/build-push-action from 2 to 3
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2 to 3.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-12 09:35:17 +00:00
Guilhem Saurel
940cfd9bd3 add dependabot to update actions 2022-08-12 11:34:57 +02:00
Guilhem Saurel
2d17e00ec5
Merge pull request #37 from kusold/patch-1
Add linux/arm64 platform to docker builds
2022-08-12 11:32:45 +02:00
Mike Kusold
9ca48a083e
Add linux/arm64 platform to docker builds
Publishes linux/arm64 docker images.

Relevant documentation: https://github.com/docker/build-push-action/blob/master/docs/advanced/multi-platform.md
2022-08-11 23:05:47 -06:00
Guilhem Saurel
8c59d2412e
Merge pull request #36 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-08-08 21:03:23 +02:00
pre-commit-ci[bot]
d7bcc786b0
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 5.0.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/5.0.2...5.0.4)
2022-08-08 19:00:42 +00:00
Guilhem Saurel
a7104ac2a0 details 2022-08-05 20:27:41 +02:00
Guilhem Saurel
152c80b3ff typo 2022-08-05 20:25:00 +02:00
Guilhem Saurel
ac4e00b54a badges 2022-08-05 20:19:46 +02:00
Guilhem Saurel
760e5cbb15
Merge pull request #35 from nim65s/script
Script
2022-08-02 09:06:06 +02:00
Guilhem Saurel
2ca7201346 poetry update 2022-08-02 00:08:30 +02:00
Guilhem Saurel
6c7d015b8d add missing docstring 2022-08-01 23:59:20 +02:00
Guilhem Saurel
50e93180ee test scripts 2022-08-01 23:51:29 +02:00
Guilhem Saurel
7b9605a363 document scripts 2022-08-01 23:51:29 +02:00
Albrecht Muehlenschulte
29653fc04b adds script 2022-08-01 23:51:29 +02:00
Guilhem Saurel
0ab2507009
Merge pull request #34 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-08-01 23:39:37 +02:00
Guilhem Saurel
dd20888af4 Merge branch 'master' into pre-commit-ci-update-config 2022-08-01 23:38:08 +02:00
Guilhem Saurel
0819503d8b fix tests 2022-08-01 23:34:34 +02:00
pre-commit-ci[bot]
2fa2557076
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 4.0.1 → 5.0.2](https://github.com/PyCQA/flake8/compare/4.0.1...5.0.2)
- [github.com/asottile/pyupgrade: v2.37.2 → v2.37.3](https://github.com/asottile/pyupgrade/compare/v2.37.2...v2.37.3)
2022-08-01 19:11:04 +00:00
Guilhem Saurel
52b5ff87ea
Merge pull request #33 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-07-26 00:28:18 +02:00
pre-commit-ci[bot]
36b5e85f7e
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.37.1 → v2.37.2](https://github.com/asottile/pyupgrade/compare/v2.37.1...v2.37.2)
2022-07-25 18:37:12 +00:00
Guilhem Saurel
7f2ba3d83b
Merge pull request #31 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-07-12 00:16:55 +02:00
pre-commit-ci[bot]
363f8a497d
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.34.0 → v2.37.1](https://github.com/asottile/pyupgrade/compare/v2.34.0...v2.37.1)
2022-07-11 18:33:45 +00:00
Guilhem Saurel
d53ab56661
Merge pull request #30 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-07-04 22:39:28 +02:00
pre-commit-ci[bot]
8469e679b3
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0)
2022-07-04 18:49:48 +00:00
Guilhem Saurel
90cc262a73
Merge pull request #28 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-06-13 20:51:58 +02:00
pre-commit-ci[bot]
8e3f4e5771
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0)
- [github.com/asottile/pyupgrade: v2.32.1 → v2.34.0](https://github.com/asottile/pyupgrade/compare/v2.32.1...v2.34.0)
2022-06-13 18:44:41 +00:00
Guilhem Saurel
8c63736879
Merge pull request #27 from nim65s/dependabot/pip/httpx-0.23.0
Bump httpx from 0.18.2 to 0.23.0
2022-06-02 10:09:41 +02:00
dependabot[bot]
baa20e51eb
Bump httpx from 0.18.2 to 0.23.0
Bumps [httpx](https://github.com/encode/httpx) from 0.18.2 to 0.23.0.
- [Release notes](https://github.com/encode/httpx/releases)
- [Changelog](https://github.com/encode/httpx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/httpx/compare/0.18.2...0.23.0)

---
updated-dependencies:
- dependency-name: httpx
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-01 23:59:44 +00:00
Guilhem Saurel
f1d2d2bd68
Merge pull request #26 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-05-10 20:03:31 +02:00
pre-commit-ci[bot]
70d85af682
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.32.0 → v2.32.1](https://github.com/asottile/pyupgrade/compare/v2.32.0...v2.32.1)
2022-05-09 18:10:56 +00:00
Guilhem Saurel
395daf4630 poetry update 2022-05-06 16:28:10 +02:00
Guilhem Saurel
8d81b7ba44
Merge pull request #24 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-04-11 20:34:44 +02:00
pre-commit-ci[bot]
36b3f6f877
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0)
- [github.com/asottile/pyupgrade: v2.31.1 → v2.32.0](https://github.com/asottile/pyupgrade/compare/v2.31.1...v2.32.0)
2022-04-11 18:30:34 +00:00
Guilhem Saurel
e9b3f83cd1
Merge pull request #23 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-04-04 21:50:04 +02:00
pre-commit-ci[bot]
916ddb7d18
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.1.0 → 22.3.0](https://github.com/psf/black/compare/22.1.0...22.3.0)
2022-04-04 18:04:37 +00:00
Guilhem Saurel
bdd3940066
Merge pull request #22 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-03-14 21:18:18 +01:00
pre-commit-ci[bot]
5bc8f81d8e
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.31.0 → v2.31.1](https://github.com/asottile/pyupgrade/compare/v2.31.0...v2.31.1)
2022-03-14 19:46:21 +00:00
Guilhem Saurel
11ee2ec2f4 Release v3.3.0 2022-03-04 00:36:46 +01:00
Guilhem Saurel
b66b9717e4 pyupgrade 2022-03-04 00:13:57 +01:00
Guilhem Saurel
c4e0bb76e9 poetry update 2022-03-04 00:11:35 +01:00
Guilhem Saurel
14398e4b7d changelog 2022-03-04 00:06:32 +01:00
Guilhem Saurel
42317f74d9
Merge pull request #21 from GhislainC/master
Add gitlab formatter
2022-03-04 00:05:11 +01:00
Ghislain Chatras
f6bf150c7f
Add gitlab formatter
Only for Google Chat and Microsoft Teams notification integrations
2022-03-03 19:17:40 +01:00
Guilhem Saurel
e91865f175
Merge pull request #19 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-02-10 17:25:15 +01:00
pre-commit-ci[bot]
a3607c5ee2
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.0.1 → v4.1.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.0.1...v4.1.0)
- [github.com/psf/black: 21.12b0 → 22.1.0](https://github.com/psf/black/compare/21.12b0...22.1.0)
2022-01-31 18:21:12 +00:00
f4808719b0 refactor(formatter/pingdom): build message content line-by-line
feat(formatter/pingdom): add tags to output (if set)
2022-01-12 12:08:53 -05:00
8c18c2054e refactor(formatter/buildbot): cleanup redundancy in logic 2022-01-11 22:22:20 -05:00
97e10df1a2 docs: format cleanup 2021-12-14 23:19:51 -05:00
630dc98974
docs: cleanup formatters as a table 2021-12-14 23:17:15 -05:00
7b94c80bbd docs: add buildbot and generic formatter details 2021-12-14 22:58:30 -05:00
5048ef8c4c
Merge branch 'nim65s:master' into master 2021-12-11 23:28:15 -05:00
Guilhem Saurel
ff10e58cba
Merge pull request #18 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-12-06 19:13:49 +01:00
pre-commit-ci[bot]
6032dfe836
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.11b1 → 21.12b0](https://github.com/psf/black/compare/21.11b1...21.12b0)
2021-12-06 17:55:15 +00:00
affe16c605 fix(buildbot): add else to handle non start/success statuses 2021-12-01 23:44:59 -05:00
92b6c3618b
Merge branch 'nim65s:master' into master 2021-12-01 23:37:48 -05:00
0c8c5faad0 feat(buildbot): add buildbot formatter 2021-12-01 23:36:57 -05:00
Guilhem Saurel
d799fc18a0
Merge pull request #17 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-11-25 16:05:21 +01:00
pre-commit-ci[bot]
0ac65ba895
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.10b0 → 21.11b1](https://github.com/psf/black/compare/21.10b0...21.11b1)
2021-11-22 17:47:55 +00:00
e42aa941e5 feat: add room to api config 2021-11-09 08:57:46 -05:00
778ebcdad9 feat: add slack formatter 2021-11-09 01:41:44 -05:00
e0a407acd9 feat: add generic formatter that will just return json sent 2021-11-09 01:41:26 -05:00
93f84859b4 feat: add config file handling 2021-11-05 10:01:32 -04:00
b5912a8b6c tests: add pingdom test and supporting json examples 2021-11-05 10:00:17 -04:00
d964f75c84 fix: correct missed envar for api_keys 2021-11-04 13:58:12 -04:00
a3638fdc75 update changelog 2021-11-04 13:51:42 -04:00
ab5927bfa4 feat(conf)!: allow multiple keys to be used for different endpoints 2021-11-04 13:26:54 -04:00
8e5a90ec1f refactor: clean-up 'weird' formatting 2021-11-04 00:17:24 -04:00
87ac023631 fix: correct except clauses missing exceptions 2021-11-03 23:42:44 -04:00
1ea47991e4 fix: correct exception case for invalid formatter module 2021-11-03 17:05:45 -04:00
0d92c378c4 feat: breakout formatters into their own plugin-like module to ease additonal formatters 2021-11-03 17:02:22 -04:00
d82a0ba9bd first attempt at this disaster 2021-11-03 16:12:52 -04:00
793a5e8c8c fix: correct missing colon on try 2021-11-03 15:51:05 -04:00
09fe26633f add basic functionality for pingdom formatter 2021-11-03 00:19:52 -04:00
Guilhem Saurel
afd68b791c
Merge pull request #16 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-11-02 08:38:07 +01:00
pre-commit-ci[bot]
b3004a5c8a
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.9b0 → 21.10b0](https://github.com/psf/black/compare/21.9b0...21.10b0)
2021-11-01 17:37:21 +00:00
Guilhem Saurel
2a78594861 handle M_CONSENT_NOT_GIVEN, fix #15 2021-10-12 16:23:24 +02:00
Guilhem Saurel
5a5e9655be
Merge pull request #14 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-10-11 20:41:05 +02:00
pre-commit-ci[bot]
ea91eea7ee
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 3.9.2 → 4.0.1](https://github.com/PyCQA/flake8/compare/3.9.2...4.0.1)
2021-10-11 17:23:48 +00:00
Guilhem Saurel
e148dfbad9 update README after #12 2021-09-28 11:05:32 +02:00
Guilhem Saurel
b3a2e00f43 comments 2021-09-28 10:58:32 +02:00
Guilhem Saurel
d0481d741a update changelog after #12 2021-09-28 10:54:10 +02:00
Guilhem Saurel
85dd602f5c
Merge pull request #12 from bboehmke/join-room
join room before sending message
2021-09-28 10:50:27 +02:00
Guilhem Saurel
d2a3e618f4 join room: fix error code & unit tests 2021-09-28 10:39:41 +02:00
Guilhem Saurel
d9fe01c1db
Merge pull request #13 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-09-27 20:35:33 +02:00
pre-commit-ci[bot]
5d39019ef7
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.8b0 → 21.9b0](https://github.com/psf/black/compare/21.8b0...21.9b0)
2021-09-20 17:21:38 +00:00
Benjamin Böhmke
7ffa47c267 join room before sending message 2021-09-18 12:20:57 +02:00
Guilhem Saurel
624a1b2c08 grafana formatter: smaller titles 2021-09-13 17:03:57 +02:00
Guilhem Saurel
6c5b3e1358 pre-commit: drop useless checks 2021-09-12 16:00:38 +02:00
Guilhem Saurel
df2059b012 docker: use image from docker hub 2021-09-12 15:55:10 +02:00
Guilhem Saurel
d0c4cd4227 formatters: fix github format 2021-09-12 15:55:10 +02:00
Guilhem Saurel
7aa5df3871 readme: detail 2021-09-12 15:55:10 +02:00
Guilhem Saurel
e5f3d7bfbd
Merge pull request #11 from nim65s/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-09-12 15:54:03 +02:00
pre-commit-ci[bot]
ef79d39a9f
[pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 21.7b0 → 21.8b0](https://github.com/psf/black/compare/21.7b0...21.8b0)
2021-08-30 17:11:56 +00:00
Guilhem Saurel
179ef11ae7 readme: typo 2021-08-28 00:18:42 +02:00
Guilhem Saurel
8592be257f badges: new line 2021-08-28 00:15:41 +02:00
Guilhem Saurel
0aea63903d badges: add PyPI 2021-08-28 00:11:30 +02:00
Guilhem Saurel
df8ce523cb fix changelog 2021-08-28 00:08:56 +02:00
Guilhem Saurel
58d0e83f8f Release v3.2.1 2021-08-28 00:07:36 +02:00
Guilhem Saurel
d726db6ed2 release: push 2021-08-28 00:07:30 +02:00
Guilhem Saurel
1c00ff22f3 fix changelog 2021-08-28 00:06:38 +02:00
Guilhem Saurel
7e1be831df ci: detail 2021-08-28 00:05:20 +02:00
Guilhem Saurel
7f5c8583a1 Release v3.2.0 2021-08-27 23:59:56 +02:00
Guilhem Saurel
4928ceb91a release: details 2021-08-27 23:59:49 +02:00
Guilhem Saurel
cda8898019 tests: improve coverage 2021-08-27 23:56:56 +02:00
Guilhem Saurel
6b5d6e6e87 formatters: add github 2021-08-27 23:47:07 +02:00
Guilhem Saurel
4bcdb25c80 formatters: also get headers 2021-08-27 20:05:08 +02:00
Guilhem Saurel
ac7d1d9647 typo 2021-08-27 18:39:28 +02:00
Guilhem Saurel
9a544b8f2b detail 2021-08-27 18:33:18 +02:00
Guilhem Saurel
8f215c04fd docs: release 2021-08-27 18:33:18 +02:00
Guilhem Saurel
7f20fb7ff9 split code in utils / handler / app 2021-08-27 18:33:18 +02:00
Guilhem Saurel
2b7b79971d lint: fix pydocstyle 2021-08-27 18:15:58 +02:00
Guilhem Saurel
6aaac9149d lint: pre-commit autoupdate 2021-08-27 18:15:58 +02:00
Guilhem Saurel
eb3c795368 lint: fix line length 2021-08-27 18:15:58 +02:00
Guilhem Saurel
c03ae0a571 lint: add flake8 configuration
Black allows up to 88 characters per line.

Put this configuration into a separated file, as pyproject.toml won't do
ref. https://github.com/PyCQA/flake8/issues/234
2021-08-27 18:15:58 +02:00
Guilhem Saurel
eabb446d05 ci: release also on github 2021-08-27 18:15:55 +02:00
Guilhem Saurel
6f7d38dbd7 update test for latest synapse docker image 2021-08-27 18:14:54 +02:00
Guilhem Saurel
530f40a129 setup action to publish releases on github 2021-08-07 17:54:39 +02:00
Guilhem Saurel
528940abcc fix changelog 2021-08-07 17:54:36 +02:00
Guilhem Saurel
0ccec84eef room_id can come from url, content, or parameters 2021-08-01 13:20:03 +02:00
Guilhem Saurel
c8f6c9ec28 README: document grafana usage 2021-08-01 13:20:03 +02:00
Guilhem Saurel
c07d4bfa8d add tests for grafana formatter 2021-08-01 13:20:03 +02:00
Guilhem Saurel
2d232fe1f7 add grafana formatter
This was initially designed and implemented in #4

Co-authored-by: Sven Seeberg <mail@sven-seeberg.de>
2021-08-01 13:19:58 +02:00
Guilhem Saurel
3bebc88ee2 allow direct formatted_body
This was initially designed and implemented in #6

Co-authored-by: Gerhard Bräunlich <gerhard.braeunlich@id.ethz.ch>
2021-08-01 13:19:53 +02:00
Guilhem Saurel
fa8f9b4a51 allow "key" to be passed as a parameter
This was initially designed and implemented in #4

Co-authored-by: Sven Seeberg <mail@sven-seeberg.de>
2021-08-01 13:19:39 +02:00
Guilhem Saurel
292d77274d update "text" key to "body" 2021-07-31 11:21:29 +02:00
Guilhem Saurel
8b32c972b8 typo 2021-07-18 23:08:40 +02:00
Guilhem Saurel
c86145f794 v3.1.1 2021-07-18 22:59:49 +02:00
Guilhem Saurel
a6b192fbd7 v3.1.0 2021-07-18 22:57:09 +02:00
Guilhem Saurel
59e98b99a7
Merge pull request #10 from nim65s/devel
Publish on PyPI & Docker Hub with Github Actions
2021-07-18 22:55:19 +02:00
Guilhem Saurel
71c9c6cb0e changelog 2021-07-18 22:53:29 +02:00
Guilhem Saurel
932965c8af setup action to build/publish on PyPI 2021-07-18 22:51:41 +02:00
Guilhem Saurel
dcc73dfc81 setup action to build/publish on docker hub 2021-07-18 19:55:15 +02:00
Guilhem Saurel
fbcae98390 add metadata on PyPI through pyproject.toml 2021-07-18 19:31:46 +02:00
Guilhem Saurel
8a3bbef54c v3.0.0 2021-07-18 19:01:21 +02:00
Guilhem Saurel
6943432367
Merge pull request #9 from nim65s/devel
setup packaging
2021-07-18 18:43:29 +02:00
Guilhem Saurel
febf2f857c add changelog 2021-07-18 18:41:20 +02:00
Guilhem Saurel
c9045d407d update README 2021-07-18 18:12:08 +02:00
Guilhem Saurel
922ebf5c78 poetry update 2021-07-18 16:56:59 +02:00
Guilhem Saurel
00997360eb fix permissions 2021-07-18 16:55:54 +02:00
Guilhem Saurel
bc646b9f4a update year 2021-07-18 16:55:23 +02:00
Guilhem Saurel
a974f073c9 split code 2021-07-18 16:55:16 +02:00
Guilhem Saurel
0c0a42a4c9 improve error management and tests 2021-07-14 23:25:59 +02:00
Guilhem Saurel
a0a78bbad0 improve logging 2021-07-14 23:25:59 +02:00
Guilhem Saurel
570e86941b
Merge pull request #8 from nim65s/devel
setup argparse & logging
2021-07-14 17:50:58 +02:00
Guilhem Saurel
2c8c618fe0 setup argparse & logging 2021-07-14 17:12:55 +02:00
Guilhem Saurel
044876daf6 fix badges 2021-07-13 12:28:01 +02:00
Guilhem Saurel
d952590d94
Merge pull request #7 from nim65s/devel
Setup Tests, Coverage & CI ; update tooling.
2021-07-13 11:56:28 +02:00
Guilhem Saurel
2bda1d5968 badges 2021-07-13 11:54:24 +02:00
Guilhem Saurel
e2d85eaa21 details 2021-07-13 11:43:01 +02:00
Guilhem Saurel
19ef1f4e93 black 2021-07-13 11:41:56 +02:00
Guilhem Saurel
91b23cd166 poetry 2021-07-13 11:41:56 +02:00
Guilhem Saurel
32208294ea update pre-commit 2021-07-13 11:41:51 +02:00
Guilhem Saurel
c5b7ea19ce CI: add lints 2021-07-13 11:41:17 +02:00
Guilhem Saurel
562b29c8a2 tests: generate homeserver on build 2021-07-13 11:41:16 +02:00
Guilhem Saurel
6633020fba clean tests 2021-07-13 11:41:16 +02:00
Guilhem Saurel
2d8c68665e details 2021-07-13 11:41:16 +02:00
Guilhem Saurel
6a2e0336d9 clean tests 2021-07-13 11:41:16 +02:00
Guilhem Saurel
999b824874 setup coverage 2021-07-13 11:41:16 +02:00
Guilhem Saurel
abe6497421 setup tests 2021-07-13 11:41:05 +02:00
Guilhem Saurel
fb17a87016 typo 2021-05-30 22:14:42 +02:00
Guilhem Saurel
d3d5ea7df2
Merge pull request #5 from nim65s/topic/pr/4
update aiohttp use and docs
2021-05-30 22:12:57 +02:00
Guilhem Saurel
2499832e1c use aiohttp.web.BaseRequest.path instead of rel_url
Co-authored-by: Sven Seeberg <mail@sven-seeberg.de>
2021-05-30 16:50:01 +02:00
Guilhem Saurel
98cd9362aa Add HOST to PORT configuration, and document both
Co-authored-by: Sven Seeberg <mail@sven-seeberg.de>
2021-05-30 16:42:25 +02:00
Guilhem Saurel
4506632c6f python 3.9 2021-04-12 21:05:20 +02:00
Guilhem Saurel
aad02887f1
Merge pull request #1 from homeworkprod-forks/main
Simplify code
2021-01-03 16:40:25 +01:00
Jochen Kupperschmidt
00f47f99a9 Unify use of single quotes for non-docstring string literals 2020-12-27 14:57:14 +01:00
Jochen Kupperschmidt
78b9533e2b Exit early after each request check
Keeps the "happy path" on the function's base indentation level.

Avoids carrying status and return value variables, pre-filled in
anticipation of an error, along.
2020-12-27 14:52:07 +01:00
Jochen Kupperschmidt
139ec1670c Use aiohttp.web.json_response()
Avoids explicit setting of JSON content type, handles serialization to
JSON.
2020-12-27 14:43:01 +01:00
Jochen Kupperschmidt
54baf29d51 Extract function to send message
Merges duplicated code.
2020-12-27 14:42:58 +01:00
Jochen Kupperschmidt
cf054631e8 Extract room ID into variable 2020-12-27 14:07:04 +01:00
Guilhem Saurel
6c138a65b4 try another login on connection lost 2020-07-28 21:56:05 +02:00
Guilhem Saurel
887dc95e3d handle ill-formed JSON 2020-06-08 09:59:51 +02:00
Guilhem Saurel
9883782f3a remove the need for www 2020-06-07 10:31:21 +02:00
Guilhem Saurel
9615cdf3c7 traefik v2 2020-05-13 00:29:57 +02:00
Guilhem Saurel
ce83079d59 markdown: extra
https://python-markdown.github.io/extensions/extra/

To get <pre> tags on fenced code
2020-04-20 19:18:57 +02:00
Guilhem Saurel
38e814c956 format readme 2020-03-14 18:10:32 +01:00
Guilhem Saurel
f11af8de16 update dev instructions 2020-03-14 18:09:18 +01:00
52 changed files with 5421 additions and 127 deletions

2
.flake8 Normal file
View file

@ -0,0 +1,2 @@
[flake8]
max-line-length = 88

7
.gitignore vendored Normal file
View file

@ -0,0 +1,7 @@
.coverage
.env
.mypy_cache
coverage.xml
htmlcov
**__pycache__
config.yaml

View file

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-ast
@ -11,19 +11,26 @@ repos:
- id: debug-statements
- id: detect-private-key
- id: end-of-file-fixer
- id: flake8
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-yapf
rev: v0.29.0
- repo: https://github.com/psf/black
rev: 22.10.0
hooks:
- id: yapf
- id: black
language_version: python3
- repo: https://github.com/PyCQA/pydocstyle
rev: 5.0.1
rev: 6.1.1
hooks:
- id: pydocstyle
- repo: https://gitlab.com/smop/pre-commit-hooks
rev: v1.0.0
args:
- --ignore=D200,D203,D212
- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: check-poetry
- id: check-gitlab-ci
- id: flake8
- repo: https://github.com/asottile/pyupgrade
rev: v3.2.2
hooks:
- id: pyupgrade
args:
- --py38-plus

103
CHANGELOG.md Normal file
View file

@ -0,0 +1,103 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v3.5.0] - 2022-09-07
- 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 formatted_body to bypass markdown with direct
[matrix-custom-HTML](https://matrix.org/docs/spec/client_server/r0.6.1#m-room-message-msgtypes)
- allow "key" to be passed as a parameter
- allow to use a sha256 HMAC hex digest with the key instead of the raw key
- allow "room_id" to be passed as a parameter or with the data
- rename "text" to "body".
- Publish releases also on github from github actions
- fix tests for recent synapse docker image
## [v3.1.1] - 2021-07-18
## [v3.1.0] - 2021-07-18
- Publish on PyPI & Docker Hub with Github Actions
in [#10](https://github.com/nim65s/matrix-webhook/pull/10)
by [@nim65s](https://github.com/nim65s)
## [v3.0.0] - 2021-07-18
- Simplify code
in [#1](https://github.com/nim65s/matrix-webhook/pull/1)
by [@homeworkprod](https://github.com/homeworkprod)
- Update aiohttp use and docs
in [#5](https://github.com/nim65s/matrix-webhook/pull/5)
by [@svenseeberg](https://github.com/svenseeberg)
- Setup Tests, Coverage & CI ; update tooling
in [#7](https://github.com/nim65s/matrix-webhook/pull/7)
by [@nim65s](https://github.com/nim65s)
- Setup argparse & logging
in [#8](https://github.com/nim65s/matrix-webhook/pull/8)
by [@nim65s](https://github.com/nim65s)
- Setup packaging
in [#9](https://github.com/nim65s/matrix-webhook/pull/9)
by [@nim65s](https://github.com/nim65s)
## [v2.0.0] - 2020-03-14
- Update to matrix-nio & aiohttp & markdown
## [v1.0.0] - 2020-02-14
- First release with matrix-client & http.server
[Unreleased]: https://github.com/nim65s/matrix-webhook/compare/v3.5.0...master
[v3.5.0]: https://github.com/nim65s/matrix-webhook/compare/v3.4.0...v3.5.0
[v3.4.0]: https://github.com/nim65s/matrix-webhook/compare/v3.3.0...v3.4.0
[v3.3.0]: https://github.com/nim65s/matrix-webhook/compare/v3.2.1...v3.3.0
[v3.2.1]: https://github.com/nim65s/matrix-webhook/compare/v3.2.0...v3.2.1
[v3.2.0]: https://github.com/nim65s/matrix-webhook/compare/v3.1.1...v3.2.0
[v3.1.1]: https://github.com/nim65s/matrix-webhook/compare/v3.1.0...v3.1.1
[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,9 +1,9 @@
FROM python:3.8-slim
FROM python:3.9
EXPOSE 4785
RUN pip3 install --no-cache-dir markdown matrix-nio
RUN pip install --no-cache-dir markdown matrix-nio
ADD matrix_webhook.py /
ADD matrix_webhook matrix_webhook
CMD /matrix_webhook.py
ENTRYPOINT ["python", "-m", "matrix_webhook"]

View file

@ -1,4 +1,4 @@
Copyright (c) 2019-2020 tetaneutral.net All rights reserved.
Copyright (c) 2019-2021 tetaneutral.net All rights reserved.
BSD 2 Clause License

105
README.md
View file

@ -2,30 +2,74 @@
Post a message to a matrix room with a simple HTTP POST
## Configuration
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
Create a matrix user for the bot, make it join the rooms you want it to talk into, and then set the following
environment variables:
## Install
For now, clone this repo and run `pip install .`
## Start
Create a matrix user for the bot, and launch this app with the following arguments and/or environment variables
(environment variables update defaults, arguments take precedence):
```
matrix-webhook -h
# OR
python -m matrix_webhook -h
# OR
docker run --rm -it nim65s/matrix-webhook -h
```
```
usage: python -m matrix_webhook [-h] [-H HOST] [-P PORT] [-u MATRIX_URL]
[-i MATRIX_ID] [-p MATRIX_PW] [-k API_KEYS]
[-c CONFIG] [-v]
Configuration for Matrix Webhook.
options:
-h, --help show this help message and exit
-H HOST, --host HOST host to listen to. Default: `''`. Environment
variable: `HOST`
-P PORT, --port PORT port to listed to. Default: 4785. Environment
variable: `PORT`
-u MATRIX_URL, --matrix-url MATRIX_URL
matrix homeserver url. Default: `https://matrix.org`.
Environment variable: `MATRIX_URL`
-i MATRIX_ID, --matrix-id MATRIX_ID
matrix user-id. Required. Environment variable:
`MATRIX_ID`
-p MATRIX_PW, --matrix-pw MATRIX_PW
matrix password. Required. Environment variable:
`MATRIX_PW`
-k API_KEYS, --api-keys API_KEYS
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
```
- `MATRIX_URL`: the url of the matrix homeserver
- `MATRIX_ID`: the user id of the bot on this server
- `MATRIX_PW`: the password for this user
- `API_KEY`: a secret to share with the users of the service
## Dev
```
pip3 install --user matrix-client
./matrix_webhook.py
poetry install
# or python3 -m pip install --user markdown matrix-nio
python3 -m matrix_webhook
```
## Prod
A `docker-compose.yml` is provided:
- Use [Traefik](https://traefik.io/) on the `web` docker network, eg. with
[proxyta.net](https://framagit.org/oxyta.net/proxyta.net)
- Put the configuration into a `.env` file
- Configure your DNS for `${CHATONS_SERVICE:-matrixwebhook}.${CHATONS_DOMAIN:-localhost}` **and**
`www.${CHATONS_SERVICE:-matrixwebhook}.${CHATONS_DOMAIN:-localhost}`
- Configure your DNS for `${CHATONS_SERVICE:-matrixwebhook}.${CHATONS_DOMAIN:-localhost}`
```
docker-compose up -d
@ -34,10 +78,45 @@ docker-compose up -d
## Test / Usage
```
curl -d '{"text":"new contrib from toto: [44](http://radio.localhost/map/#44)", "key": "secret"}' 'matrixwebhook.localhost/!DPrUlnwOhBEfYwsDLh:matrix.org'
curl -d '{"body":"new contrib from toto: [44](http://radio.localhost/map/#44)", "key": "secret"}' \
'http://matrixwebhook.localhost/!DPrUlnwOhBEfYwsDLh:matrix.org'
```
(or localhost:4785 without docker)
### Formatters
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>`
| formatter | description | key location |
| -- | - | - |
| 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
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
[#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org?via=laas.fr&via=tetaneutral.net&via=aen.im)
[#matrix-webhook:tetaneutral.net](https://matrix.to/#/!DPrUlnwOhBEfYwsDLh:matrix.org)
## Unit tests
```
docker-compose -f test.yml up --exit-code-from tests --force-recreate --build
```

19
config-dist.yaml Normal file
View file

@ -0,0 +1,19 @@
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,6 +6,7 @@ networks:
services:
bot:
image: nim65s/matrix-webhook
build: .
restart: unless-stopped
env_file:
@ -14,4 +15,4 @@ services:
- web
labels:
traefik.enable: "true"
traefik.frontend.rule: "Host: ${CHATONS_SERVICE:-matrixwebhook}.${CHATONS_DOMAIN:-localhost}, www.${CHATONS_SERVICE:-matrixwebhook}.${CHATONS_DOMAIN:-localhost}"
traefik.http.routers.matrix-webhook.rule: "Host(`${CHATONS_SERVICE:-matrixwebhook}.${CHATONS_DOMAIN:-localhost}`)"

18
docs/release.md Normal file
View file

@ -0,0 +1,18 @@
# Publish a new release
A github actions handle the build of the release archives, and push them to PyPI and Github Releases.
To trigger it, we just need to:
1. use poetry to update the version number
2. update the changelog
3. git commit
4. git tag
5. git push
6. git push --tags
For this, an helper script is provided:
```bash
./docs/release.sh [patch|minor|major|x.y.z]
```

22
docs/release.sh Executable file
View file

@ -0,0 +1,22 @@
#!/bin/bash -eux
# ./docs/release.sh [patch|minor|major|x.y.z]
[[ $(basename "$PWD") == docs ]] && cd ..
OLD=$(poetry version -s)
poetry version "$1"
NEW=$(poetry version -s)
DATE=$(date +%Y-%m-%d)
sed -i "/^## \[Unreleased\]/a \\\n## [v$NEW] - $DATE" 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
git add pyproject.toml CHANGELOG.md
git commit -m "Release v$NEW"
git tag -s "v$NEW" -m "Release v$NEW"
git push
git push --tags

View file

@ -1,98 +0,0 @@
#!/usr/bin/env python3
"""
Matrix Webhook.
Post a message to a matrix room with a simple HTTP POST
v1: matrix-client & http.server
v2: matrix-nio & aiohttp & markdown
"""
import asyncio
import json
import os
from http import HTTPStatus
from signal import SIGINT, SIGTERM
from markdown import markdown
from aiohttp import web
from nio import AsyncClient
SERVER_ADDRESS = ('', int(os.environ.get('PORT', 4785)))
MATRIX_URL = os.environ.get('MATRIX_URL', 'https://matrix.org')
MATRIX_ID = os.environ.get('MATRIX_ID', '@wwm:matrix.org')
MATRIX_PW = os.environ['MATRIX_PW']
API_KEY = os.environ['API_KEY']
CLIENT = AsyncClient(MATRIX_URL, MATRIX_ID)
async def handler(request):
"""
Coroutine given to the server, st. it knows what to do with an HTTP request.
This one handles a POST, checks its content, and forwards it to the matrix room.
"""
data = await request.read()
data = json.loads(data.decode())
status, ret = HTTPStatus.BAD_REQUEST, 'I need a json dict with text & key'
if all(key in data for key in ['text', 'key']):
status, ret = HTTPStatus.UNAUTHORIZED, 'I need the good "key"'
if data['key'] == API_KEY:
status, ret = HTTPStatus.OK, 'OK'
await CLIENT.room_send(room_id=str(request.rel_url)[1:],
message_type="m.room.message",
content={
"msgtype": "m.text",
"body": data['text'],
"format": "org.matrix.custom.html",
"formatted_body": markdown(data['text']),
})
return web.Response(text='{"status": %i, "ret": "%s"}' % (status, ret),
content_type='application/json',
status=status)
async def main(event):
"""
Launch main coroutine.
matrix client login & start web server
"""
await CLIENT.login(MATRIX_PW)
server = web.Server(handler)
runner = web.ServerRunner(server)
await runner.setup()
site = web.TCPSite(runner, *SERVER_ADDRESS)
await site.start()
# Run until we get a shutdown request
await event.wait()
# Cleanup
await runner.cleanup()
await CLIENT.close()
def terminate(event, signal):
"""Close handling stuff."""
event.set()
asyncio.get_event_loop().remove_signal_handler(signal)
def run():
"""Launch everything."""
loop = asyncio.get_event_loop()
event = asyncio.Event()
for sig in (SIGINT, SIGTERM):
loop.add_signal_handler(sig, terminate, event, sig)
loop.run_until_complete(main(event))
loop.close()
if __name__ == '__main__':
run()

View file

@ -0,0 +1,15 @@
"""Matrix Webhook module entrypoint."""
import logging
from . import app, conf
def main():
"""Start everything."""
log_format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s"
logging.basicConfig(level=50 - 10 * conf.VERBOSE, format=log_format)
app.run()
if __name__ == "__main__":
main()

56
matrix_webhook/app.py Normal file
View file

@ -0,0 +1,56 @@
"""Matrix Webhook app."""
import asyncio
import logging
from signal import SIGINT, SIGTERM
from aiohttp import web
from . import conf, handler, utils
LOGGER = logging.getLogger("matrix_webhook.app")
async def main(event):
"""
Launch main coroutine.
matrix client login & start web server
"""
LOGGER.info(f"Log in {conf.MATRIX_ID=} on {conf.MATRIX_URL=}")
await utils.CLIENT.login(conf.MATRIX_PW)
server = web.Server(handler.matrix_webhook)
runner = web.ServerRunner(server)
await runner.setup()
LOGGER.info(f"Binding on {conf.SERVER_ADDRESS=}")
site = web.TCPSite(runner, *conf.SERVER_ADDRESS)
await site.start()
# Run until we get a shutdown request
await event.wait()
# Cleanup
await runner.cleanup()
await utils.CLIENT.close()
def terminate(event, signal):
"""Close handling stuff."""
event.set()
asyncio.get_event_loop().remove_signal_handler(signal)
def run():
"""Launch everything."""
LOGGER.info("Starting...")
loop = asyncio.get_event_loop()
event = asyncio.Event()
for sig in (SIGINT, SIGTERM):
loop.add_signal_handler(sig, terminate, event, sig)
loop.run_until_complete(main(event))
LOGGER.info("Closing...")
loop.close()

105
matrix_webhook/conf.py Normal file
View file

@ -0,0 +1,105 @@
"""Configuration for Matrix Webhook."""
import argparse
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.add_argument(
"-H",
"--host",
default=os.environ.get("HOST", ""),
help="host to listen to. Default: `''`. Environment variable: `HOST`",
)
parser.add_argument(
"-P",
"--port",
type=int,
default=os.environ.get("PORT", 4785),
help="port to listed to. Default: 4785. Environment variable: `PORT`",
)
parser.add_argument(
"-u",
"--matrix-url",
default=os.environ.get("MATRIX_URL", "https://matrix.org"),
help="matrix homeserver url. Default: `https://matrix.org`. "
"Environment variable: `MATRIX_URL`",
)
parser.add_argument(
"-i",
"--matrix-id",
help="matrix user-id. Required. Environment variable: `MATRIX_ID`",
)
parser.add_argument(
"-p",
"--matrix-pw",
help="matrix password. Required. Environment variable: `MATRIX_PW`",
)
parser.add_argument(
"-k",
"--api-keys",
help="comma separated list of shared secrets to use this service. Required. Environment variable: `API_KEYS`",
)
parser.add_argument(
"-c",
"--config",
help="configuration file. Default: `config.yaml`",
)
parser.add_argument(
"-v", "--verbose", action="count", default=0, help="increment verbosity level"
)
args = parser.parse_args()
if args.config:
with open(args.config) as f:
config = yaml.safe_load(f)
SERVER_ADDRESS = (config["hostname"], config["port"])
MATRIX_URL = config["matrix"]["url"]
MATRIX_ID = config["matrix"]["id"]
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,8 @@
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

@ -0,0 +1,34 @@
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

@ -0,0 +1,36 @@
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

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

View file

@ -0,0 +1,15 @@
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

@ -0,0 +1,12 @@
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

@ -0,0 +1,9 @@
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

@ -0,0 +1,64 @@
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

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

91
matrix_webhook/handler.py Normal file
View file

@ -0,0 +1,91 @@
"""Matrix Webhook main request handler."""
import json
import logging
from http import HTTPStatus
from hmac import HMAC
import importlib
from markdown import markdown
from . import conf, utils
LOGGER = logging.getLogger("matrix_webhook.handler")
async def matrix_webhook(request):
"""
Coroutine given to the server, st. it knows what to do with an HTTP request.
This one handles a POST, checks its content, and forwards it to the matrix room.
"""
LOGGER.debug(f"Handling {request=}")
data_b = await request.read()
try:
data = json.loads(data_b.decode())
except json.decoder.JSONDecodeError:
return utils.create_json_response(HTTPStatus.BAD_REQUEST, "Invalid JSON")
# legacy naming
if "text" in data and "body" not in data:
data["body"] = data["text"]
# allow key to be passed as a parameter
if "key" in request.rel_url.query and "key" not in data:
data["key"] = request.rel_url.query["key"]
if "formatter" in request.rel_url.query:
try:
format = request.rel_url.query["formatter"]
plugin = importlib.import_module(f"matrix_webhook.formatters.{format}", "formatter")
data = plugin.formatter(data, request.headers)
except ModuleNotFoundError:
return utils.create_json_response(
HTTPStatus.BAD_REQUEST, "Unknown formatter"
)
if "room_id" in request.rel_url.query and "room_id" not in data:
data["room_id"] = request.rel_url.query["room_id"]
if "room_id" not in data:
data["room_id"] = request.path.lstrip("/")
# If we get a good SHA-256 HMAC digest,
# we can consider that the sender has the right API key
if "digest" in data:
if data["digest"] == HMAC(conf.API_KEY.encode(), data_b, "sha256").hexdigest():
data["key"] = conf.API_KEY
else: # but if there is a wrong digest, an informative error should be provided
return utils.create_json_response(
HTTPStatus.UNAUTHORIZED, "Invalid SHA-256 HMAC digest"
)
missing = []
for key in ["body", "key", "room_id"]:
if key not in data or not data[key]:
missing.append(key)
if missing:
return utils.create_json_response(
HTTPStatus.BAD_REQUEST, f"Missing {', '.join(missing)}"
)
if data["key"] not in conf.API_KEYS:
return utils.create_json_response(HTTPStatus.UNAUTHORIZED, "Invalid API key")
if "formatted_body" in data:
formatted_body = data["formatted_body"]
else:
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 = {
"msgtype": "m.text",
"body": data["body"],
"format": "org.matrix.custom.html",
"formatted_body": formatted_body,
}
return await utils.send_room_message(data["room_id"], content)

78
matrix_webhook/utils.py Normal file
View file

@ -0,0 +1,78 @@
"""Matrix Webhook utils."""
import logging
from http import HTTPStatus
from aiohttp import web
from nio import AsyncClient
from nio.exceptions import LocalProtocolError
from nio.responses import RoomSendError, JoinError
from . import conf
ERROR_MAP = {
"M_FORBIDDEN": HTTPStatus.FORBIDDEN,
"M_CONSENT_NOT_GIVEN": HTTPStatus.FORBIDDEN,
}
LOGGER = logging.getLogger("matrix_webhook.utils")
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):
"""Create a JSON response."""
LOGGER.debug(f"Creating json response: {status=}, {ret=}")
response_data = {"status": status, "ret": ret}
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):
"""Send a message to a room."""
LOGGER.debug(f"Sending room message in {room_id=}: {content=}")
for _ in range(10):
try:
resp = await CLIENT.room_send(
room_id=room_id, message_type="m.room.message", content=content
)
if isinstance(resp, RoomSendError):
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 create_json_response(HTTPStatus.OK, "OK")
except LocalProtocolError as e:
LOGGER.error(f"Send error: {e}")
LOGGER.warning("Trying again")
return create_json_response(HTTPStatus.GATEWAY_TIMEOUT, "Homeserver not responding")

2577
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

5
package.json Normal file
View file

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

1040
poetry.lock generated Normal file

File diff suppressed because it is too large Load diff

36
pyproject.toml Normal file
View file

@ -0,0 +1,36 @@
[tool.poetry]
name = "matrix-webhook"
version = "3.5.0"
description = "Post a message to a matrix room with a simple HTTP POST"
authors = ["Guilhem Saurel <guilhem.saurel@laas.fr>"]
license = "BSD-2-Clause"
readme = "README.md"
homepage = "https://github.com/nim65s/matrix-webhook"
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]
python = "^3.8"
Markdown = "^3.3.4"
matrix-nio = ">=0.18.3,<0.21.0"
PyYAML = "^6.0"
[tool.poetry.dev-dependencies]
httpx = "^0.23.0"
black = "^22.8.0"
coverage = "^6.4.4"
pydocstyle = "^6.1.1"
flake8 = "^5.0.4"
pyupgrade = "^2.31.0"
[tool.pydocstyle]
ignore = ["D200", "D203", "D204", "D212"]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
matrix-webhook = "matrix_webhook.__main__:main"

12
test.yml Normal file
View file

@ -0,0 +1,12 @@
version: '3'
services:
tests:
build:
context: .
dockerfile: tests/Dockerfile
entrypoint: ""
env_file:
- tests/.env
volumes:
- ./:/app

4
tests/.env Normal file
View file

@ -0,0 +1,4 @@
MATRIX_URL=http://tests
MATRIX_ID=bot
MATRIX_PW=pw
API_KEY=ak

36
tests/Dockerfile Normal file
View file

@ -0,0 +1,36 @@
# Leverage a synapse base to be able to:
# "from synapse._scripts.register_new_matrix_user import request_registration"
FROM matrixdotorg/synapse
# The config dir defaults to /data which is a volume made to keep data.
# Here, we want to trash those (and avoid the permission issues) by using something else
ENV SYNAPSE_CONFIG_DIR=/srv SYNAPSE_DATA_DIR=/srv SYNAPSE_SERVER_NAME=tests SYNAPSE_REPORT_STATS=no
# Generate configuration and keys for synapse
WORKDIR $SYNAPSE_CONFIG_DIR
RUN chown -R 991:991 . \
&& /start.py generate \
&& 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
RUN pip install --no-cache-dir markdown matrix-nio httpx coverage
WORKDIR /app
CMD ./tests/start.py -vvv

1
tests/__init__.py Normal file
View file

@ -0,0 +1 @@
"""Make this directory a valid module for unittests autodiscover to work."""

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"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

@ -0,0 +1 @@
{"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

@ -0,0 +1,22 @@
{
"dashboardId":1,
"evalMatches":[
{
"value":1,
"metric":"Count",
"tags":{}
}
],
"imageUrl":"https://grafana.com/assets/img/blog/mixed_styles.png",
"message":"Notification Message",
"orgId":1,
"panelId":2,
"ruleId":1,
"ruleName":"Panel Title alert",
"ruleUrl":"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1",
"state":"alerting",
"tags":{
"tag name":"tag value"
},
"title":"[Alerting] Panel Title alert"
}

View file

@ -0,0 +1,41 @@
{
"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

@ -0,0 +1,33 @@
{
"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

@ -0,0 +1,36 @@
{
"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

@ -0,0 +1,34 @@
{
"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
}
}
}

122
tests/start.py Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env python
"""Entry point to start an instrumentalized bot for coverage and run tests."""
import argparse
import logging
from os import environ
from subprocess import Popen, run
from time import time
from unittest import main
import httpx
import yaml
from synapse._scripts.register_new_matrix_user import request_registration
BOT_URL = "http://localhost:4785"
KEY, MATRIX_URL, MATRIX_ID, MATRIX_PW = (
environ[v] for v in ["API_KEY", "MATRIX_URL", "MATRIX_ID", "MATRIX_PW"]
)
FULL_ID = f'@{MATRIX_ID}:{MATRIX_URL.split("/")[2]}'
LOGGER = logging.getLogger("matrix-webhook.tests.start")
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"-v", "--verbose", action="count", default=0, help="increment verbosity level"
)
def bot_req(
req=None,
key=None,
room_id=None,
params=None,
key_as_param=False,
room_as_parameter=False,
):
"""Bot requests boilerplate."""
if params is None:
params = {}
if key is not None:
if key_as_param:
params["key"] = key
else:
req["key"] = key
if room_as_parameter:
params["room_id"] = room_id
url = BOT_URL if room_id is None or room_as_parameter else f"{BOT_URL}/{room_id}"
return httpx.post(url, params=params, json=req).json()
def wait_available(url: str, key: str, timeout: int = 10) -> bool:
"""Wait until a service answer correctly or timeout."""
def check_json(url: str, key: str) -> bool:
"""
Ensure a service at a given url answers with valid json including a certain key.
"""
try:
data = httpx.get(url).json()
return key in data
except httpx.ConnectError:
return False
start = time()
while True:
if check_json(url, key):
return True
if time() > start + timeout:
return False
def run_and_test():
"""Launch the bot and its tests."""
# Start the server, and wait for it
LOGGER.info("Spawning synapse")
srv = Popen(
[
"python",
"-m",
"synapse.app.homeserver",
"--config-path",
"/srv/homeserver.yaml",
]
)
if not wait_available(f"{MATRIX_URL}/_matrix/client/r0/login", "flows"):
return False
# Register a user for the bot.
LOGGER.info("Registering the bot")
with open("/srv/homeserver.yaml") as f:
secret = yaml.safe_load(f.read()).get("registration_shared_secret", None)
request_registration(MATRIX_ID, MATRIX_PW, MATRIX_URL, secret, admin=True)
# Start the bot, and wait for it
LOGGER.info("Spawning the bot")
bot = Popen(["coverage", "run", "-m", "matrix_webhook", "-vvvvv"])
if not wait_available(BOT_URL, "status"):
return False
# Run the main unittest module
LOGGER.info("Runnig unittests")
ret = main(module=None, exit=False).result.wasSuccessful()
LOGGER.info("Stopping synapse")
srv.terminate()
# TODO Check what the bot says when the server is offline
# print(bot_req({'data': 'bye'}, KEY), {'status': 200, 'ret': 'OK'})
LOGGER.info("Stopping the bot")
bot.terminate()
LOGGER.info("Processing coverage")
for cmd in ["report", "html", "xml"]:
run(["coverage", cmd])
return ret
if __name__ == "__main__":
args = parser.parse_args()
log_format = "%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s"
logging.basicConfig(level=50 - 10 * args.verbose, format=log_format)
exit(not run_and_test())

130
tests/test_github.py Normal file
View file

@ -0,0 +1,130 @@
"""Test module for grafana formatter."""
import unittest
import httpx
import nio
from .start import BOT_URL, FULL_ID, MATRIX_ID, MATRIX_PW, MATRIX_URL
SHA256 = "fd7522672889385736be8ffc86d1f8de2e15668864f49af729b5c63e5e0698c4"
def headers(sha256=SHA256, event="push"):
"""Mock headers from github webhooks."""
return {
# 'Request URL': 'https://bot.saurel.me/room?formatter=github',
# 'Request method': 'POST',
"Accept": "*/*",
"content-type": "application/json",
"User-Agent": "GitHub-Hookshot/8d33975",
"X-GitHub-Delivery": "636b9b1c-0761-11ec-8a8a-5e435c5ac4f4",
"X-GitHub-Event": event,
"X-GitHub-Hook-ID": "311845633",
"X-GitHub-Hook-Installation-Target-ID": "171114171",
"X-GitHub-Hook-Installation-Target-Type": "repository",
"X-Hub-Signature": "sha1=ea68fdfcb2f328aaa8f50d176f355e5d4fc95d94",
"X-Hub-Signature-256": f"sha256={sha256}",
}
class GithubFormatterTest(unittest.IsolatedAsyncioTestCase):
"""Github formatter test class."""
async def test_github_notification(self):
"""Send a mock github webhook, 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_github_push.json", "rb") as f:
example_github_push = f.read().strip()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={
"formatter": "github",
},
content=example_github_push,
headers=headers(event="something else"),
).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.formatted_body,
"<p>notification from github</p>",
)
async def test_github_push(self):
"""Send a mock github push webhook, 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_github_push.json", "rb") as f:
example_github_push = f.read().strip()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={
"formatter": "github",
},
content=example_github_push,
headers=headers(),
).json(),
{"status": 200, "ret": "OK"},
)
sync = await client.sync()
messages = await client.room_messages(room.room_id, sync.next_batch)
await client.close()
before = "ac7d1d9647008145e9d0cf65d24744d0db4862b8"
after = "4bcdb25c809391baaabc264d9309059f9f48ead2"
GH = "https://github.com"
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'4bcdb25c8093">{before}{after}</a>:</p>\n<ul>\n<li>'
expected += f'<a href="{GH}/nim65s/matrix-webhook/commit/{after}">'
expected += "formatters: also get headers</a></li>\n</ul>"
message = messages.chunk[0]
self.assertEqual(message.sender, FULL_ID)
self.assertEqual(
message.formatted_body,
expected,
)
async def test_github_wrong_digest(self):
"""Send a mock github push webhook with a wrong digest."""
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
with open("tests/example_github_push.json", "rb") as f:
example_github_push = f.read().strip()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={
"formatter": "github",
},
content=example_github_push,
headers=headers("wrong digest"),
).json(),
{"status": 401, "ret": "Invalid SHA-256 HMAC digest"},
)
await client.close()

View file

@ -0,0 +1,54 @@
"""
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

@ -0,0 +1,55 @@
"""
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",
)

46
tests/test_grafana.py Normal file
View file

@ -0,0 +1,46 @@
"""
Test module for 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, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL
class GrafanaFormatterTest(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.json") as f:
example_grafana_request = f.read()
self.assertEqual(
httpx.post(
f"{BOT_URL}/{room.room_id}",
params={"formatter": "grafana", "key": KEY},
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)
self.assertEqual(
message.body,
"#### [Alerting] Panel Title alert\nNotification Message\n\n* Count: 1\n",
)

51
tests/test_grafana_9x.py Normal file
View file

@ -0,0 +1,51 @@
"""
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

@ -0,0 +1,51 @@
"""
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)

44
tests/test_pingdom.py Normal file
View file

@ -0,0 +1,44 @@
"""
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",
)

169
tests/tests.py Normal file
View file

@ -0,0 +1,169 @@
"""Main test module."""
import unittest
import nio
from .start import FULL_ID, KEY, MATRIX_ID, MATRIX_PW, MATRIX_URL, bot_req
class BotTest(unittest.IsolatedAsyncioTestCase):
"""Main test class."""
def test_errors(self):
"""Check the bot's error paths."""
self.assertEqual(bot_req(), {"status": 400, "ret": "Invalid JSON"})
self.assertEqual(
bot_req({"toto": 3}),
{"status": 400, "ret": "Missing body, key, room_id"},
)
self.assertEqual(
bot_req({"body": 3}, "wrong_key", "wrong_room"),
{"status": 401, "ret": "Invalid API key"},
)
self.assertEqual(
bot_req({"body": 3}, "wrong_key", "wrong_room", key_as_param=True),
{"status": 401, "ret": "Invalid API key"},
)
self.assertEqual(
bot_req({"body": 3}, KEY, params={"formatter": "wrong_formatter"}),
{"status": 400, "ret": "Unknown formatter"},
)
# TODO: if the client from matrix_webhook has olm support,
# this won't be a 403 from synapse, but a LocalProtocolError from matrix_webhook
self.assertEqual(
bot_req({"body": 3}, KEY, "wrong_room"),
{"status": 400, "ret": "wrong_room was not legal room ID or room alias"},
)
self.assertEqual(
bot_req({"body": 3}, KEY, "wrong_room", key_as_param=True),
{"status": 400, "ret": "wrong_room was not legal room ID or room alias"},
)
async def test_message(self):
"""Send a markdown message with the old format, and check the result."""
text = "# Hello"
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
self.assertEqual(
bot_req({"text": text}, KEY, room.room_id), {"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, text)
self.assertEqual(message.formatted_body, "<h1>Hello</h1>")
async def test_room_id_req(self):
"""Send a markdown message in a room given as data, and check the result."""
body = "# Hello"
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
self.assertEqual(
bot_req({"body": body, "room_id": room.room_id}, KEY, room.room_id),
{"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, body)
self.assertEqual(message.formatted_body, "<h1>Hello</h1>")
async def test_room_id_parameter(self):
"""Send a markdown message in a room given as parameter."""
body = "# Hello"
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
self.assertEqual(
bot_req({"body": body}, KEY, room.room_id, room_as_parameter=True),
{"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, body)
self.assertEqual(message.formatted_body, "<h1>Hello</h1>")
async def test_markdown_body(self):
"""Send a markdown message, and check the result."""
body = "# Hello"
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
self.assertEqual(
bot_req({"body": body}, KEY, room.room_id), {"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, body)
self.assertEqual(message.formatted_body, "<h1>Hello</h1>")
async def test_formatted_body(self):
"""Send a formatted message, and check the result."""
body = "Formatted message"
formatted_body = "<del>markdown</del><strong>Formatted</strong> message"
messages = []
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
self.assertEqual(
bot_req(
{"body": body, "formatted_body": formatted_body}, KEY, room.room_id
),
{"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, body)
self.assertEqual(message.formatted_body, formatted_body)
async def test_reconnect(self):
"""Check the reconnecting path."""
client = nio.AsyncClient(MATRIX_URL, MATRIX_ID)
await client.login(MATRIX_PW)
room = await client.room_create()
await client.logout(all_devices=True)
await client.close()
self.assertEqual(
bot_req({"body": "Re"}, KEY, room.room_id),
{"status": 200, "ret": "OK"},
)