feat: initial commit
This commit is contained in:
commit
1f2425b9d6
3 changed files with 103 additions and 0 deletions
16
LICENSE
Normal file
16
LICENSE
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
Copyright 2023 s3lph
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or
|
||||||
|
substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||||
|
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||||
|
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
|
||||||
|
OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
43
README.md
Normal file
43
README.md
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Semaphore Webhook Server
|
||||||
|
|
||||||
|
A small Python webservice to extend the API of [Semaphore UI][semui] with a "webhook" enpoint to start tasks.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Put the script onto your Semaphore server.
|
||||||
|
1. Run it e.g. as a systemd service.
|
||||||
|
1. Configure the reverse proxy to proxy a path of your choice to `http://localhost:3042/webhook` rather than Semaphore.
|
||||||
|
- Apache example:
|
||||||
|
|
||||||
|
ProxyPass /.well-known !
|
||||||
|
# Webhook server - must be before semaphre
|
||||||
|
ProxyPass /webhook http://localhost:3042/webhook
|
||||||
|
ProxyPassReverse /webhook http://localhost:3042/webhook
|
||||||
|
# Semaphore
|
||||||
|
ProxyPass /api/ws ws://localhost:3000/api/ws
|
||||||
|
ProxyPass / http://localhost:3000/
|
||||||
|
ProxyPassReverse / http://localhost:3000/
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The easiest to use this webhook endpoint, simply GET or POST the `/webhook` endpoint with the `project_id` and `template_id` parameters:
|
||||||
|
|
||||||
|
GET /webhook?project_id=1&template_id=1 HTTP/1.1
|
||||||
|
Host: semaphore.example.org
|
||||||
|
Authorization: Bearer ...
|
||||||
|
|
||||||
|
These two parameters are required. The total set of supporter parameters are:
|
||||||
|
|
||||||
|
- `project_id` (**required**): int
|
||||||
|
- `template_id` (**required**): int
|
||||||
|
- `debug`: bool, `true` or `false`
|
||||||
|
- `dry_run`: bool
|
||||||
|
- `diff`: bool
|
||||||
|
|
||||||
|
Each request to the webhook server is transformed into a request to the Semaphore API's [`POST /project/{project_id}/tasks` endpoint][endpoint].
|
||||||
|
|
||||||
|
The authorization header is passed on to Semaphore unmodified.
|
||||||
|
|
||||||
|
|
||||||
|
[semui]: https://www.semui.co/
|
||||||
|
[endpoint]: https://www.semui.co/api-docs/#/project/post_project__project_id__tasks
|
44
semaphore-webhook-server.py
Executable file
44
semaphore-webhook-server.py
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import bottle
|
||||||
|
|
||||||
|
|
||||||
|
@bottle.get('/webhook')
|
||||||
|
@bottle.post('/webhook')
|
||||||
|
def webhook():
|
||||||
|
project_id = int(bottle.request.query.project_id)
|
||||||
|
payload = {
|
||||||
|
'project_id': project_id,
|
||||||
|
'environment': '{}',
|
||||||
|
}
|
||||||
|
if 'template_id' in bottle.request.query:
|
||||||
|
payload['template_id'] = int(bottle.request.query.template_id)
|
||||||
|
if 'debug' in bottle.request.query:
|
||||||
|
payload['debug'] = bottle.request.query.debug == 'true'
|
||||||
|
if 'dry_run' in bottle.request.query:
|
||||||
|
payload['dry_run'] = bottle.request.query.dry_run == 'true'
|
||||||
|
if 'diff' in bottle.request.query:
|
||||||
|
payload['diff'] = bottle.request.query.diff == 'true'
|
||||||
|
baseurl = f'https://{bottle.request.urlparts.netloc}'
|
||||||
|
url = os.path.join(baseurl, 'api/project', str(project_id), 'tasks')
|
||||||
|
headers = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': bottle.request.headers['Authorization'],
|
||||||
|
'X-Forwarded-For': bottle.request.headers['X-Forwarded-For'],
|
||||||
|
'X-Forwarded-Host': bottle.request.headers['X-Forwarded-Host'],
|
||||||
|
'X-Forwarded-Server': bottle.request.headers['X-Forwarded-Server'],
|
||||||
|
}
|
||||||
|
req = urllib.request.Request(url, data=json.dumps(payload).encode(), method='POST', headers=headers)
|
||||||
|
try:
|
||||||
|
resp = urllib.request.urlopen(req)
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
bottle.abort(e.code, e.reason)
|
||||||
|
bottle.abort(204, 'No Content')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
bottle.run(host='::1', port=3042)
|
Loading…
Reference in a new issue