Skip to content
Snippets Groups Projects
Commit 45a27374 authored by Quentin Duchemin's avatar Quentin Duchemin
Browse files

Merge branch 'wekan-update' into 'master'

Bump wekan and add a tool to filter hooks by activity type

See merge request picasoft/projets/dockerfiles!52
parents 032ff861 c841403a
No related branches found
No related tags found
No related merge requests found
......@@ -18,3 +18,9 @@ Il suffit d'un `docker-compose up -d`.
Il suffit de mettre à jour les tags des images dans le [docker-compose.yml](./docker-compose.yml), et de vérifier au niveau de la page des [Releases](https://github.com/wekan/wekan/releases) s'il y a eu des modifications.
On pourra aussi consulter le Compose du dépôt officiel pour effectuer les nouvelles configurations.
### Hooks
Pour configurer des hooks, on pourra consulter la [documentation utilisateur](https://wiki.picasoft.net/doku.php?id=technique:adminsys:wekan:hooks).
Voir aussi [le README de filter-hooks](./filter-hooks) pour le code permettant de filter les hooks par activité.
version: '2.4'
version: '3.7'
volumes:
wekan:
networks:
docker_default:
name: "docker_default"
external: true
wekan:
name: "wekan"
services:
wekan-db:
image: mongo:4.0.12
image: mongo:4.2
container_name: wekan-db
restart: always
command: mongod --smallfiles --oplogSize 128
command: mongod --oplogSize 128
expose:
- 27017
volumes:
- wekan:/data/db
networks:
- wekan
restart: unless-stopped
wekan-app:
image: wekanteam/wekan:v3.57
image: wekanteam/wekan:v4.25
container_name: wekan-app
labels:
- "traefik.frontend.rule=Host:kanban.picasoft.net"
- "traefik.port=8080"
- "traefik.enable=true"
restart: always
links:
- wekan-db:wekan-db
environment:
- MONGO_URL=mongodb://wekan-db:27017/wekan
- ROOT_URL=https://kanban.picasoft.net
- CARD_OPENED_WEBHOOK_ENABLED=false
- WEBHOOKS_ATTRIBUTES=cardId,listId,user
# ==== WEKAN API AND EXPORT BOARD ====
# Wekan Export Board works when WITH_API=true.
# https://github.com/wekan/wekan/wiki/REST-API
- WITH_API=true
# ==== PASSWORD BRUTE FORCE PROTECTION ====
- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=10
- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60
- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15
depends_on:
- wekan-db
labels:
traefik.frontend.rule: Host:kanban.picasoft.net
traefik.port: 8080
traefik.enable: true
networks:
- docker_default
- wekan
restart: unless-stopped
wekan-filter-hooks:
image: registry.picasoft.net/wekan-filter-hooks:v0.1
build: ./filter-hooks
container_name: wekan-filter-hooks
environment:
- KANBAN_URL=https://kanban.picasoft.net
networks:
- docker_default
labels:
traefik.enable: true
traefik.port: 5000
traefik.frontend.rule: Host:kanban.picasoft.net;Path:/forward_hooks
restart: unless-stopped
## v0.1
Initial release.
FROM python:3
LABEL maintainer="quentinduchemin@tuta.io"
RUN mkdir /app
COPY . /app
WORKDIR /app
RUN pip install --no-cache-dir -r requirements.txt
CMD [ "gunicorn", "--bind", "0.0.0.0:5000", "--reload", "main:app" ]
## Filter et faire suivre les hooks Wekan
Wekan a une fonctionnalité pour [déclencher un *hook*](https://github.com/wekan/wekan/pull/1119) lors de l'activité sur un board.
Grâce aux hooks, on peut configurer Mattermost pour recevoir un message sur un canal donné dès qu'une activité est détectée.
Le souci est que Wekan [déclenche les hooks sortants pour chaque activité](https://github.com/wekan/wekan/wiki/Webhook-data), même les plus insignifiantes, ce qui spamme le canal.
L'idée est donc de passer par un intermédiaire pour filter les hooks selon le type d'activité, et ne transmettre que les activités intéressantes. C'est ce que fait ce petit outil. Le code est très basique voire bancal et pourra être amélioré.
### Lancement
Il suffit de construire l'image et de lancer l'outil.
On peut aussi le faire à la main :
```
gunicorn --bind 0.0.0.0:5000 --reload main:app
```
### Utilisation
Supposons que dans une application type Mattermost, on configure un hook **entrant** qui peut recevoir des requêtes `POST` de Wekan. Cette URL est désignée par `URL_HOOK`.
Supposons que cet outil soit déployé à l'adresse `https://URL_FILTER`.
Dans Wekan, l'URL du hook sortant sera alors (à la place de `URL_HOOK`) :
```
https://URL_FILTER/forward_hooks?url=URL_HOOK
```
Quand il reçoit une requête, l'outil examine le hook et voit si le type d'activité correspond à une liste pré-définie.
Si oui, il construit un objet JSON avec une description en français de la forme :
```json
{
"text": "Machin a créé la carte truc dans TODO"
}
```
Puis l'envoye à `URL_HOOK`. La plupart des outils type Mattermost sont compatibles avec ce format (c'est celui utilisé par Wekan, d'ailleurs). Sur Mattermost, un message sera posté avec `text` pour contenu.
### Type d'activité pris en compte
Les hooks "pertinents" sont définis directement [dans le code](./main.py).
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import os
import logging
import requests
from flask import Flask, request
# Configure logging
log_format = '%(asctime)s [%(levelname)s] ' \
'%(message)s (%(filename)s:%(lineno)d)'
logging.basicConfig(
format=log_format,
datefmt='%Y/%m/%d %H:%M:%S',
level=logging.INFO
)
# Run Flask
app = Flask(__name__)
# List of hooks to forward to the real hook URL
# See https://github.com/wekan/wekan/wiki/Webhook-data
allowed_hooks = {
'act-createCard': 'créé',
'act-moveCard': 'déplacé',
'act-addComment': 'commenté'
}
@app.route('/forward_hooks', methods=['POST'])
def filter_hook():
hook_data = json.loads(request.data)
event = hook_data['description']
# Check if we must forward hook
if event in allowed_hooks:
# Retrieve card URL
card_url_pos = hook_data['text'].index(os.environ['KANBAN_URL'])
card_url = hook_data['text'][card_url_pos:]
# Text like '<user> a <verbe> la carte <nom>' with a link
text = f'{hook_data["user"]} a [{allowed_hooks[event]} la carte **{hook_data["card"]}**]({card_url})'
# If card moved, add origin and destination lists
if event == 'act-moveCard':
# Yes, I'm ashamed of this ugly parsing, thanks for asking.
from_list = hook_data['text'].split('from list')[1].split('"')[1]
to_list = hook_data['text'].split('to list')[1].split('"')[1]
text += f' de {from_list} vers {to_list}.'
# If there is a new comment, add it as a citation
elif event == 'act-addComment':
text += f'\n>{hook_data["comment"]}'
# If new card, add the list it belongs to
elif event == 'act-createCard':
to_list = hook_data['text'].split('to list')[1].split('"')[1]
text += f' dans {to_list}'
# Forward the hook
r = requests.post(request.args['url'], json={'text': text})
if r.status_code != 200:
logging.error(f'Unable to forward hook : {r}')
else:
logging.info('Hook forwarded.')
# HTTP No Content
return ('', 204)
Flask
gunicorn
requests
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment