-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 6ef260e
Showing
11 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
src/settings | ||
src/settings.ex | ||
*.pyc | ||
*/*/__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.venv/ | ||
.vscode/ | ||
src/settings/ | ||
*.pyc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
FROM alpine:3.10 | ||
|
||
COPY requirements.txt src/ /app/wekan-scheduler/ | ||
|
||
WORKDIR /app/wekan-scheduler | ||
|
||
RUN apk add --no-cache ca-certificates git python3 && \ | ||
pip3 install -r requirements.txt && \ | ||
apk del git | ||
|
||
CMD ["./scheduler.py" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
# Wekan Scheduler | ||
|
||
Pythonic card scheduler for [Wekan](https://wekan.github.io/). | ||
|
||
|
||
## About | ||
|
||
The purpose of *wekan-scheduler* is to create cards regulary for repeating | ||
tasks. It uses [pycron](https://github.com/kipe/pycron) for schedule | ||
configuration and uses the | ||
[wekan-python-api-client](https://github.com/wekan/wekan-python-api-client) to | ||
access the [Wekan REST API](https://wekan.github.io/api/v3.00/). | ||
|
||
|
||
## Configuration | ||
|
||
You should create a dedicated user in *Wekan* to be used to access the *Wekan | ||
REST API* in the `PASSWORD` auth backend (`LDAP` seems not to work). Add the | ||
user to the boards where it should create cards. | ||
|
||
There are three configuration files required in the `settings` directory: | ||
|
||
- **\_\_init\_\_.py** | ||
should left empty, required to make the config a python package | ||
- **config.py** | ||
contains the base configuration to access the *Wekan REST API*: | ||
```python | ||
CONFIG = { | ||
# URL for Wekan | ||
'api_url': "https://localhost/kanban", | ||
|
||
# REST API auth headers | ||
'api_auth': {"username": "api", "password": "xxxxxxxx"}, | ||
|
||
# Verify X.509 certificate for api_url? | ||
'api_verify': True, | ||
|
||
# Sleep for N seconds between schedules (should be at least 60s) | ||
'sleep': 300, | ||
} | ||
``` | ||
- **schedules.py** | ||
contains your custom schedules: | ||
```python | ||
SCHEDULES = [ | ||
# daily work card | ||
{ | ||
# cron-like schedule (pycron) | ||
'schedule': '0 8 * * 1-5', | ||
|
||
# target board and list | ||
'board': '<board-id>', | ||
'list': '<list-id>', | ||
|
||
# required fields | ||
'card': { | ||
'title': 'check for donuts', | ||
}, | ||
|
||
# more card details | ||
'details': { | ||
'description': 'first-come, first-served', | ||
'color': 'pink', | ||
'labelIds': ['<label-id>'], | ||
'members': ['<user-id>'], | ||
}, | ||
}, | ||
] | ||
``` | ||
|
||
There is a `get-ids.py` helper script to dump the various IDs of your Wekan | ||
instance. This helps to find the required various IDs for your schedules: | ||
|
||
``` | ||
{'boards': {'4b5BmHE2CL8wt8brR': {'labels': {'2kRvzr': 'gray[crumbly]', | ||
'jdiYNd': 'plum[goo]', | ||
'xhAdgq': 'lime[tasty]'}, | ||
'lists': {'zzujS93kd8982k30y': 'Backlog', | ||
'loxc9Dll,masd300a': 'Eating', | ||
'qosLk34SDKsmsksd3': 'Done', | ||
'lasd93lkdaskSAKsl': 'Problem'}, | ||
'title:': 'Donut Fighters'}}, | ||
'users': [{'_id': 'Ksd34pKsW23wFg9Sx', 'username': 'admin'}, | ||
{'_id': 'lS9lkasd3mAusdkSK', 'username': 'api'}]} | ||
``` | ||
|
||
|
||
## Deploy | ||
|
||
The recommended way to run *wekan-scheduler* is to use the provided auto-build | ||
docker images from [Docker | ||
Hub](https://cloud.docker.com/u/liske/repository/docker/liske/wekan-scheduler). | ||
Run it with *docker-compose*: | ||
|
||
```yaml | ||
version: '3' | ||
|
||
services: | ||
scheduler: | ||
image: liske/wekan-scheduler:0.1 | ||
restart: always | ||
volumes: | ||
- ./settings:/app/wekan-scheduler/settings:ro | ||
``` | ||
If you are running *Wekan* on *Docker* you may add *wekan-scheduler* to your | ||
existing `docker-compose.yml`: | ||
|
||
```yaml | ||
# ... | ||
scheduler: | ||
image: liske/wekan-scheduler:0.1 | ||
restart: always | ||
volumes: | ||
- ./scheduler/settings:/app/wekan-scheduler/settings:ro | ||
depends_on: | ||
- wekan | ||
# ... | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
certifi==2019.9.11 | ||
chardet==3.0.4 | ||
idna==2.8 | ||
pycron==1.0.0 | ||
requests==2.22.0 | ||
urllib3==1.25.6 | ||
git+https://github.com/wekan/wekan-python-api-client.git#egg=wekanapi&subdirectory=src |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#!/usr/bin/env python3 | ||
|
||
from pprint import pprint | ||
import wekan_scheduler | ||
from settings.config import CONFIG | ||
|
||
|
||
def get_ids(): | ||
api = wekan_scheduler.api() | ||
|
||
ids = { | ||
'users': api.api_call("/api/users"), | ||
'boards': {}, | ||
} | ||
|
||
for board in api.get_user_boards(): | ||
if board.title == 'Templates': | ||
continue | ||
|
||
ids['boards'][board.id] = {'title:': board.title, 'labels': {}, 'lists': {}} | ||
|
||
data = api.api_call("/api/boards/{}".format(board.id)) | ||
for label in data['labels']: | ||
ids['boards'][board.id]['labels'][label['_id']] = '{}[{}]'.format(label['color'], label.get('name', '')) | ||
|
||
for cardlist in board.get_cardslists(): | ||
ids['boards'][board.id]['lists'][cardlist.id] = cardlist.title | ||
|
||
return ids | ||
|
||
|
||
if __name__ == "__main__": | ||
if CONFIG['api_verify']: | ||
pprint(get_ids()) | ||
else: | ||
with wekan_scheduler.no_ssl_verification(): | ||
pprint(get_ids()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import time | ||
import datetime | ||
import sys | ||
import pycron | ||
import wekan_scheduler | ||
from settings.config import CONFIG | ||
from settings.schedules import SCHEDULES | ||
|
||
|
||
from pprint import pprint | ||
|
||
def create_card(_sched): | ||
# create a work copy | ||
sched = _sched.copy() | ||
|
||
# Wekan API wrapper | ||
api = wekan_scheduler.api() | ||
|
||
# add authorId if missing | ||
if not 'authorId' in sched['card']: | ||
sched['card']['authorId'] = api.user_id | ||
|
||
# add swimlaneId if missing | ||
if not 'swimlaneId' in sched['card']: | ||
sl = api.api_call("/api/boards/{}/swimlanes".format(sched['board'])) | ||
sched['card']['swimlaneId'] = sl[0]['_id'] | ||
|
||
# create card | ||
res = api.api_call( | ||
"/api/boards/{}/lists/{}/cards".format(sched['board'], sched['list']), sched['card']) | ||
|
||
# add details | ||
if '_id' in res: | ||
api_response = api.session.put( | ||
"{}{}".format(api.api_url, "/api/boards/{}/lists/{}/cards/{}".format( | ||
sched['board'], sched['list'], res['_id'])), | ||
data=sched['details'], | ||
headers={"Authorization": "Bearer {}".format(api.token)}, | ||
proxies=api.proxies | ||
) | ||
|
||
sinces = {} | ||
for idx, sched in enumerate(SCHEDULES): | ||
sinces[idx] = datetime.datetime.now() | ||
|
||
def main(): | ||
while True: | ||
for idx, sched in enumerate(SCHEDULES): | ||
ts = datetime.datetime.now() | ||
if pycron.has_been(sched['schedule'], sinces[idx]): | ||
try: | ||
print("#{} RUNNING SCHEDULE".format(idx + 1)) | ||
create_card(sched) | ||
sinces[idx] = ts | ||
print("#{} => DONE".format(idx + 1)) | ||
except: | ||
print("#{} => EXCEPTION: {}".format(idx + 1, sys.exc_info())) | ||
|
||
time.sleep(CONFIG['sleep']) | ||
|
||
if __name__ == "__main__": | ||
if CONFIG['api_verify']: | ||
main() | ||
else: | ||
with wekan_scheduler.no_ssl_verification(): | ||
main() |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
CONFIG = { | ||
# URL for wekan | ||
'api_url': "https://localhost/kanban", | ||
|
||
# REST API auth headers | ||
'api_auth': {"username": "api", "password": "xxxxxxxx"}, | ||
|
||
# Verify X.509 certificate for api_url? | ||
'api_verify': True, | ||
|
||
# Sleep for N seconds between schedules (should be at least 60s) | ||
'sleep': 300, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
SCHEDULES = [ | ||
# daily work card | ||
{ | ||
# cron-like schedule (pycron) | ||
'schedule': '0 8 * * 1-5', | ||
|
||
# target board and list | ||
'board': '<board-id>', | ||
'list': '<list-id>', | ||
|
||
# required fields | ||
'card': { | ||
'title': 'check for donuts', | ||
}, | ||
|
||
# more card details | ||
'details': { | ||
'description': 'first-come, first-served', | ||
'color': 'pink', | ||
'labelIds': ['<label-id>'], | ||
'members': ['<user-id>'], | ||
}, | ||
}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import warnings | ||
import contextlib | ||
import requests | ||
from urllib3.exceptions import InsecureRequestWarning | ||
from wekanapi import WekanApi | ||
from settings.config import CONFIG | ||
|
||
|
||
old_merge_environment_settings = requests.Session.merge_environment_settings | ||
|
||
|
||
@contextlib.contextmanager | ||
def no_ssl_verification(): | ||
opened_adapters = set() | ||
|
||
def merge_environment_settings(self, url, proxies, stream, verify, cert): | ||
# Verification happens only once per connection so we need to close | ||
# all the opened adapters once we're done. Otherwise, the effects of | ||
# verify=False persist beyond the end of this context manager. | ||
opened_adapters.add(self.get_adapter(url)) | ||
|
||
settings = old_merge_environment_settings( | ||
self, url, proxies, stream, verify, cert) | ||
settings['verify'] = False | ||
|
||
return settings | ||
|
||
requests.Session.merge_environment_settings = merge_environment_settings | ||
|
||
try: | ||
with warnings.catch_warnings(): | ||
warnings.simplefilter('ignore', InsecureRequestWarning) | ||
yield | ||
finally: | ||
requests.Session.merge_environment_settings = old_merge_environment_settings | ||
|
||
for adapter in opened_adapters: | ||
try: | ||
adapter.close() | ||
except: | ||
pass | ||
|
||
|
||
_api = None | ||
|
||
|
||
def api(): | ||
global _api | ||
if _api is None: | ||
_api = WekanApi(CONFIG['api_url'], | ||
CONFIG['api_auth'], ) | ||
return _api |