From c5f7852f54fee085cce380e19704f19fb73c4a52 Mon Sep 17 00:00:00 2001 From: Oscar Hemelaar Date: Wed, 22 Oct 2025 13:26:51 +0200 Subject: [PATCH 1/6] build docker image --- .github/workflows/build-docker.yaml | 60 +++++++++++++++++++++++++++++ Dockerfile | 19 +++++++++ 2 files changed, 79 insertions(+) create mode 100644 .github/workflows/build-docker.yaml create mode 100644 Dockerfile diff --git a/.github/workflows/build-docker.yaml b/.github/workflows/build-docker.yaml new file mode 100644 index 0000000..4d4bcff --- /dev/null +++ b/.github/workflows/build-docker.yaml @@ -0,0 +1,60 @@ +# from https://docs.github.com/fr/actions/use-cases-and-examples/publishing-packages/publishing-docker-images +name: Create and publish a Docker image + +# Configures this workflow to run every time a change is pushed to the branch called `release`. +on: + push: + branches: ["main"] + tags: ["*"] + +# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. +jobs: + build-and-push-image: + runs-on: ubuntu-latest + # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. + permissions: + contents: read + packages: write + attestations: write + id-token: write + # + steps: + - name: Checkout repository + uses: actions/checkout@v4 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. + # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see [Usage](https://github.com/docker/build-push-action#usage) in the README of the `docker/build-push-action` repository. + # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds). + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..125c320 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM dock.mau.dev/maubot/maubot:v0.5.2-standalone AS deps + +WORKDIR /plugin + +RUN apk add --no-cache py3-dateutil-pyc=2.9.0-r1 + +FROM deps AS build + +COPY . . + +RUN mkdir build extract && mbc build -o build && unzip build/* -d extract + +FROM deps + +COPY --from=build --chmod=444 /plugin/extract/ /plugin + +ENTRYPOINT ["python"] +CMD ["-m", "maubot.standalone"] + From 5f14a9c4c60a9f1db076b9aa33e2b47da7ec3eb0 Mon Sep 17 00:00:00 2001 From: Oscar Hemelaar Date: Wed, 22 Oct 2025 14:12:00 +0200 Subject: [PATCH 2/6] fix handling grafana test notification --- .gitignore | 3 ++- alert_examples/grafana_test.json | 44 ++++++++++++++++++++++++++++++++ alertbot.py | 2 ++ test.sh | 2 ++ tests/test_alertbot.py | 17 +++++++----- 5 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 alert_examples/grafana_test.json create mode 100755 test.sh diff --git a/.gitignore b/.gitignore index 1623bf6..1784216 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.mbp *.svg -.idea/ \ No newline at end of file +.idea/ +__pycache__ diff --git a/alert_examples/grafana_test.json b/alert_examples/grafana_test.json new file mode 100644 index 0000000..92dd4b8 --- /dev/null +++ b/alert_examples/grafana_test.json @@ -0,0 +1,44 @@ +{ + "receiver": "test", + "status": "firing", + "alerts": [ + { + "status": "firing", + "labels": { + "alertname": "TestAlert", + "instance": "Grafana" + }, + "annotations": { + "summary": "Notification test" + }, + "startsAt": "2025-10-22T11:47:38.678838846Z", + "endsAt": "0001-01-01T00:00:00Z", + "generatorURL": "", + "fingerprint": "57c6d9296de2ad39", + "silenceURL": "https://domain.tld/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTestAlert&matcher=instance%3DGrafana", + "dashboardURL": "", + "panelURL": "", + "values": null, + "valueString": "[ metric='foo' labels={instance=bar} value=10 ]" + } + ], + "groupLabels": { + "alertname": "TestAlert", + "instance": "Grafana" + }, + "commonLabels": { + "alertname": "TestAlert", + "instance": "Grafana" + }, + "commonAnnotations": { + "summary": "Notification test" + }, + "externalURL": "https://domain.tld/", + "version": "1", + "groupKey": "test-57c6d9296de2ad39-1761133658", + "truncatedAlerts": 0, + "orgId": 15, + "title": "[FIRING:1] TestAlert Grafana ", + "state": "alerting", + "message": "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = TestAlert\n - instance = Grafana\nAnnotations:\n - summary = Notification test\nSilence: https://domain.tld/alerting/silence/new?alertmanager=grafana&matcher=alertname%3DTestAlert&matcher=instance%3DGrafana\n" +} diff --git a/alertbot.py b/alertbot.py index 6247439..d8c9a39 100644 --- a/alertbot.py +++ b/alertbot.py @@ -152,6 +152,8 @@ def uptime_kuma_alert_to_markdown(alert_data: dict): def dict_to_markdown(alert_data: dict): md = "" + if alert_data is None: + return md for key_or_dict in alert_data: try: alert_data[key_or_dict] diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..45a03e8 --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +#!/bin/sh +python -m unittest discover tests diff --git a/tests/test_alertbot.py b/tests/test_alertbot.py index 6947af8..dcf3de1 100644 --- a/tests/test_alertbot.py +++ b/tests/test_alertbot.py @@ -3,23 +3,23 @@ import json examples = [{"name": "Grafana Alert", - "filepath": "../alert_examples/grafana_alert.json", + "filepath": "alert_examples/grafana_alert.json", "expected_response": "", "type": "grafana-alert"}, {"name": "Grafana Resolved", - "filepath": "../alert_examples/grafana_resolved.json", + "filepath": "alert_examples/grafana_resolved.json", "expected_response": "", "type": "grafana-resolved"}, {"name": "Uptime Kuma 503 Alert", - "filepath": "../alert_examples/uptime-kuma-503-alert.json", + "filepath": "alert_examples/uptime-kuma-503-alert.json", "expected_response": "", "type": "uptime-kuma-alert"}, {"name": "Prometheus Alert", - "filepath": "../alert_examples/prometheus_alert.json", + "filepath": "alert_examples/prometheus_alert.json", "expected_response": "", "type": "prometheus-alert"}, {"name": "Slack Alert", - "filepath": "../alert_examples/slack-webhook.json", + "filepath": "alert_examples/slack-webhook.json", "expected_response": "", "type": "slack-webhook"}, ] @@ -31,9 +31,14 @@ def test_classification(self): print(f"Example: {example['name']}") with open(example["filepath"]) as file: alert_data = json.load(file) - found_type = alertbot.find_alert_type(alert_data) + found_type = alertbot.get_alert_type(alert_data) self.assertEqual(found_type, example["type"]) + def test_grafana_test_msg(self): + with open('alert_examples/grafana_test.json') as f: + alert_data = json.load(f) + render = alertbot.get_alert_messages(alert_data) + if __name__ == '__main__': unittest.main() From 86dd0dc679c49ffeb390b397770fd50707d8a04d Mon Sep 17 00:00:00 2001 From: Oscar Hemelaar Date: Wed, 22 Oct 2025 14:18:15 +0200 Subject: [PATCH 3/6] bump version --- maubot.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maubot.yaml b/maubot.yaml index 82cc339..a9021ef 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: de.hyteck.alertbot -version: 1.1.2 +version: 1.1.3 license: AGPL-3.0-or-later modules: - alertbot From 575cce9d280a65be95877c12f226de358d82ffa0 Mon Sep 17 00:00:00 2001 From: Oscar Hemelaar Date: Mon, 24 Nov 2025 14:41:32 +0100 Subject: [PATCH 4/6] update maubot base version --- Dockerfile | 2 +- maubot.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 125c320..44b4e71 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM dock.mau.dev/maubot/maubot:v0.5.2-standalone AS deps +FROM dock.mau.dev/maubot/maubot:v0.6.0-standalone AS deps WORKDIR /plugin diff --git a/maubot.yaml b/maubot.yaml index a9021ef..f2971bc 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: de.hyteck.alertbot -version: 1.1.3 +version: 1.1.4 license: AGPL-3.0-or-later modules: - alertbot From ef8386486d29423e1c76a894e55735e0008c4d27 Mon Sep 17 00:00:00 2001 From: Oscar Hemelaar Date: Tue, 25 Nov 2025 09:26:28 +0100 Subject: [PATCH 5/6] remove management frontend --- Dockerfile | 4 ++++ maubot.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 44b4e71..efd0c8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,10 @@ RUN mkdir build extract && mbc build -o build && unzip build/* -d extract FROM deps +# no need for the management frontend +# contains libraries triggering our security scanners +RUN rm -rf /opt/maubot/maubot/management/frontend + COPY --from=build --chmod=444 /plugin/extract/ /plugin ENTRYPOINT ["python"] diff --git a/maubot.yaml b/maubot.yaml index f2971bc..ec70188 100644 --- a/maubot.yaml +++ b/maubot.yaml @@ -1,6 +1,6 @@ maubot: 0.1.0 id: de.hyteck.alertbot -version: 1.1.4 +version: 1.1.5 license: AGPL-3.0-or-later modules: - alertbot From a5569bede8eb9cb172358d8653d050c3f3afad4f Mon Sep 17 00:00:00 2001 From: Oscar Hemelaar Date: Thu, 22 Jan 2026 16:53:34 +0100 Subject: [PATCH 6/6] feat: remove attestations --- .github/workflows/build-docker.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/build-docker.yaml b/.github/workflows/build-docker.yaml index 4d4bcff..e1684ab 100644 --- a/.github/workflows/build-docker.yaml +++ b/.github/workflows/build-docker.yaml @@ -51,10 +51,3 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see [Using artifact attestations to establish provenance for builds](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds). - - name: Generate artifact attestation - uses: actions/attest-build-provenance@v2 - with: - subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}} - subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: true